* add xdgmime from freedesktop.org
[citadel.git] / libcitadel / lib / xdgmime / xdgmimeglob.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmimeglob.c: Private file.  Datastructure for storing the globs.
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  *
6  * Copyright (C) 2003  Red Hat, Inc.
7  * Copyright (C) 2003  Jonathan Blandford <jrb@alum.mit.edu>
8  *
9  * Licensed under the Academic Free License version 2.0
10  * Or under the following terms:
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31
32 #include "xdgmimeglob.h"
33 #include "xdgmimeint.h"
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <assert.h>
37 #include <string.h>
38 #include <fnmatch.h>
39
40 #ifndef FALSE
41 #define FALSE   (0)
42 #endif
43
44 #ifndef TRUE
45 #define TRUE    (!FALSE)
46 #endif
47
48 typedef struct XdgGlobHashNode XdgGlobHashNode;
49 typedef struct XdgGlobList XdgGlobList;
50
51 struct XdgGlobHashNode
52 {
53   xdg_unichar_t character;
54   const char *mime_type;
55   XdgGlobHashNode *next;
56   XdgGlobHashNode *child;
57 };
58 struct XdgGlobList
59 {
60   const char *data;
61   const char *mime_type;
62   XdgGlobList *next;
63 };
64
65 struct XdgGlobHash
66 {
67   XdgGlobList *literal_list;
68   XdgGlobHashNode *simple_node;
69   XdgGlobList *full_list;
70 };
71
72
73 /* XdgGlobList
74  */
75 static XdgGlobList *
76 _xdg_glob_list_new (void)
77 {
78   XdgGlobList *new_element;
79
80   new_element = calloc (1, sizeof (XdgGlobList));
81
82   return new_element;
83 }
84
85 /* Frees glob_list and all of it's children */
86 static void
87 _xdg_glob_list_free (XdgGlobList *glob_list)
88 {
89   XdgGlobList *ptr, *next;
90
91   ptr = glob_list;
92
93   while (ptr != NULL)
94     {
95       next = ptr->next;
96
97       if (ptr->data)
98         free ((void *) ptr->data);
99       if (ptr->mime_type)
100         free ((void *) ptr->mime_type);
101       free (ptr);
102
103       ptr = next;
104     }
105 }
106
107 static XdgGlobList *
108 _xdg_glob_list_append (XdgGlobList *glob_list,
109                        void        *data,
110                        const char  *mime_type)
111 {
112   XdgGlobList *new_element;
113   XdgGlobList *tmp_element;
114
115   new_element = _xdg_glob_list_new ();
116   new_element->data = data;
117   new_element->mime_type = mime_type;
118   if (glob_list == NULL)
119     return new_element;
120
121   tmp_element = glob_list;
122   while (tmp_element->next != NULL)
123     tmp_element = tmp_element->next;
124
125   tmp_element->next = new_element;
126
127   return glob_list;
128 }
129
130 #if 0
131 static XdgGlobList *
132 _xdg_glob_list_prepend (XdgGlobList *glob_list,
133                         void        *data,
134                         const char  *mime_type)
135 {
136   XdgGlobList *new_element;
137
138   new_element = _xdg_glob_list_new ();
139   new_element->data = data;
140   new_element->next = glob_list;
141   new_element->mime_type = mime_type;
142
143   return new_element;
144 }
145 #endif
146
147 /* XdgGlobHashNode
148  */
149
150 static XdgGlobHashNode *
151 _xdg_glob_hash_node_new (void)
152 {
153   XdgGlobHashNode *glob_hash_node;
154
155   glob_hash_node = calloc (1, sizeof (XdgGlobHashNode));
156
157   return glob_hash_node;
158 }
159
160 static void
161 _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node,
162                           int depth)
163 {
164   int i;
165   for (i = 0; i < depth; i++)
166     printf (" ");
167
168   printf ("%c", (char)glob_hash_node->character);
169   if (glob_hash_node->mime_type)
170     printf (" - %s\n", glob_hash_node->mime_type);
171   else
172     printf ("\n");
173   if (glob_hash_node->child)
174     _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1);
175   if (glob_hash_node->next)
176     _xdg_glob_hash_node_dump (glob_hash_node->next, depth);
177 }
178
179 static XdgGlobHashNode *
180 _xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node,
181                             const char      *text,
182                             const char      *mime_type)
183 {
184   XdgGlobHashNode *node;
185   xdg_unichar_t character;
186
187   character = _xdg_utf8_to_ucs4 (text);
188
189   if ((glob_hash_node == NULL) ||
190       (character < glob_hash_node->character))
191     {
192       node = _xdg_glob_hash_node_new ();
193       node->character = character;
194       node->next = glob_hash_node;
195       glob_hash_node = node;
196     }
197   else if (character == glob_hash_node->character)
198     {
199       node = glob_hash_node;
200     }
201   else
202     {
203       XdgGlobHashNode *prev_node;
204       int found_node = FALSE;
205
206       /* Look for the first character of text in glob_hash_node, and insert it if we
207        * have to.*/
208       prev_node = glob_hash_node;
209       node = prev_node->next;
210
211       while (node != NULL)
212         {
213           if (character < node->character)
214             {
215               node = _xdg_glob_hash_node_new ();
216               node->character = character;
217               node->next = prev_node->next;
218               prev_node->next = node;
219
220               found_node = TRUE;
221               break;
222             }
223           else if (character == node->character)
224             {
225               found_node = TRUE;
226               break;
227             }
228           prev_node = node;
229           node = node->next;
230         }
231
232       if (! found_node)
233         {
234           node = _xdg_glob_hash_node_new ();
235           node->character = character;
236           node->next = prev_node->next;
237           prev_node->next = node;
238         }
239     }
240
241   text = _xdg_utf8_next_char (text);
242   if (*text == '\000')
243     {
244       if (node->mime_type)
245         {
246           if (strcmp (node->mime_type, mime_type))
247             {
248               XdgGlobHashNode *child;
249               int found_node = FALSE;
250               
251               child = node->child;
252               while (child && child->character == '\0')
253                 {
254                   if (strcmp (child->mime_type, mime_type) == 0)
255                     {
256                       found_node = TRUE;
257                       break;
258                     }
259                   child = child->next;
260                 }
261
262               if (!found_node)
263                 {
264                   child = _xdg_glob_hash_node_new ();
265                   child->character = '\000';
266                   child->mime_type = strdup (mime_type);
267                   child->child = NULL;
268                   child->next = node->child;
269                   node->child = child;
270                 }
271             }
272         }
273       else
274         {
275           node->mime_type = strdup (mime_type);
276         }
277     }
278   else
279     {
280       node->child = _xdg_glob_hash_insert_text (node->child, text, mime_type);
281     }
282   return glob_hash_node;
283 }
284
285 static int
286 _xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node,
287                                       const char      *file_name,
288                                       int              ignore_case,
289                                       const char      *mime_types[],
290                                       int              n_mime_types)
291 {
292   int n;
293   XdgGlobHashNode *node;
294   xdg_unichar_t character;
295
296   if (glob_hash_node == NULL)
297     return 0;
298
299   character = _xdg_utf8_to_ucs4 (file_name);
300   if (ignore_case)
301     character = _xdg_ucs4_to_lower(character);
302
303   for (node = glob_hash_node; node && character >= node->character; node = node->next)
304     {
305       if (character == node->character)
306         {
307           file_name = _xdg_utf8_next_char (file_name);
308           if (*file_name == '\000')
309             {
310               n = 0;
311
312               if (node->mime_type != NULL)
313                 mime_types[n++] = node->mime_type;
314
315               node = node->child;
316               while (n < n_mime_types && node && node->character == 0)
317                 {
318                   if (node->mime_type != NULL)
319                     mime_types[n++] = node->mime_type;
320
321                   node = node->next;
322                 }
323             }
324           else
325             {
326               n = _xdg_glob_hash_node_lookup_file_name (node->child,
327                                                         file_name,
328                                                         ignore_case,
329                                                         mime_types,
330                                                         n_mime_types);
331             }
332           return n;
333         }
334     }
335
336   return 0;
337 }
338
339 int
340 _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash,
341                                  const char  *file_name,
342                                  const char  *mime_types[],
343                                  int          n_mime_types)
344 {
345   XdgGlobList *list;
346   const char *ptr;
347   char stopchars[128];
348   int i, n;
349   XdgGlobHashNode *node;
350
351   /* First, check the literals */
352
353   assert (file_name != NULL && n_mime_types > 0);
354
355   for (list = glob_hash->literal_list; list; list = list->next)
356     {
357       if (strcmp ((const char *)list->data, file_name) == 0)
358         {
359           mime_types[0] = list->mime_type;
360           return 1;
361         }
362     }
363
364   i = 0;
365   for (node = glob_hash->simple_node; node; node = node->next)
366     {
367       if (node->character < 128)
368         stopchars[i++] = (char)node->character;
369     }
370   stopchars[i] = '\0';
371  
372   ptr = strpbrk (file_name, stopchars);
373   while (ptr)
374     {
375       n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, FALSE,
376                                                 mime_types, n_mime_types);
377       if (n > 0)
378         return n;
379       
380       n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, ptr, TRUE,
381                                                 mime_types, n_mime_types);
382       if (n > 0)
383         return n;
384       
385       ptr = strpbrk (ptr + 1, stopchars);
386     }
387
388   /* FIXME: Not UTF-8 safe */
389   n = 0;
390   for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next)
391     {
392       if (fnmatch ((const char *)list->data, file_name, 0) == 0)
393         mime_types[n++] = list->mime_type;
394     }
395
396   return n;
397 }
398
399
400
401 /* XdgGlobHash
402  */
403
404 XdgGlobHash *
405 _xdg_glob_hash_new (void)
406 {
407   XdgGlobHash *glob_hash;
408
409   glob_hash = calloc (1, sizeof (XdgGlobHash));
410
411   return glob_hash;
412 }
413
414
415 static void
416 _xdg_glob_hash_free_nodes (XdgGlobHashNode *node)
417 {
418   if (node)
419     {
420       if (node->child)
421        _xdg_glob_hash_free_nodes (node->child);
422       if (node->next)
423        _xdg_glob_hash_free_nodes (node->next);
424       if (node->mime_type)
425         free ((void *) node->mime_type);
426       free (node);
427     }
428 }
429
430 void
431 _xdg_glob_hash_free (XdgGlobHash *glob_hash)
432 {
433   _xdg_glob_list_free (glob_hash->literal_list);
434   _xdg_glob_list_free (glob_hash->full_list);
435   _xdg_glob_hash_free_nodes (glob_hash->simple_node);
436   free (glob_hash);
437 }
438
439 XdgGlobType
440 _xdg_glob_determine_type (const char *glob)
441 {
442   const char *ptr;
443   int maybe_in_simple_glob = FALSE;
444   int first_char = TRUE;
445
446   ptr = glob;
447
448   while (*ptr != '\000')
449     {
450       if (*ptr == '*' && first_char)
451         maybe_in_simple_glob = TRUE;
452       else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*')
453           return XDG_GLOB_FULL;
454
455       first_char = FALSE;
456       ptr = _xdg_utf8_next_char (ptr);
457     }
458   if (maybe_in_simple_glob)
459     return XDG_GLOB_SIMPLE;
460   else
461     return XDG_GLOB_LITERAL;
462 }
463
464 /* glob must be valid UTF-8 */
465 void
466 _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash,
467                             const char  *glob,
468                             const char  *mime_type)
469 {
470   XdgGlobType type;
471
472   assert (glob_hash != NULL);
473   assert (glob != NULL);
474
475   type = _xdg_glob_determine_type (glob);
476
477   switch (type)
478     {
479     case XDG_GLOB_LITERAL:
480       glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type));
481       break;
482     case XDG_GLOB_SIMPLE:
483       glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type);
484       break;
485     case XDG_GLOB_FULL:
486       glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type));
487       break;
488     }
489 }
490
491 void
492 _xdg_glob_hash_dump (XdgGlobHash *glob_hash)
493 {
494   XdgGlobList *list;
495   printf ("LITERAL STRINGS\n");
496   if (glob_hash->literal_list == NULL)
497     {
498       printf ("    None\n");
499     }
500   else
501     {
502       for (list = glob_hash->literal_list; list; list = list->next)
503         printf ("    %s - %s\n", (char *)list->data, list->mime_type);
504     }
505   printf ("\nSIMPLE GLOBS\n");
506   _xdg_glob_hash_node_dump (glob_hash->simple_node, 4);
507
508   printf ("\nFULL GLOBS\n");
509   if (glob_hash->full_list == NULL)
510     {
511       printf ("    None\n");
512     }
513   else
514     {
515       for (list = glob_hash->full_list; list; list = list->next)
516         printf ("    %s - %s\n", (char *)list->data, list->mime_type);
517     }
518 }
519
520
521 void
522 _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash,
523                                const char  *file_name)
524 {
525   FILE *glob_file;
526   char line[255];
527
528   glob_file = fopen (file_name, "r");
529
530   if (glob_file == NULL)
531     return;
532
533   /* FIXME: Not UTF-8 safe.  Doesn't work if lines are greater than 255 chars.
534    * Blah */
535   while (fgets (line, 255, glob_file) != NULL)
536     {
537       char *colon;
538       if (line[0] == '#')
539         continue;
540
541       colon = strchr (line, ':');
542       if (colon == NULL)
543         continue;
544       *(colon++) = '\000';
545       colon[strlen (colon) -1] = '\000';
546       _xdg_glob_hash_append_glob (glob_hash, colon, line);
547     }
548
549   fclose (glob_file);
550 }