xdgmime - support the new case sensitive flag
[platform/upstream/glib.git] / gio / 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 #include <ctype.h>
40
41 #ifndef FALSE
42 #define FALSE   (0)
43 #endif
44
45 #ifndef TRUE
46 #define TRUE    (!FALSE)
47 #endif
48
49 typedef struct XdgGlobHashNode XdgGlobHashNode;
50 typedef struct XdgGlobList XdgGlobList;
51
52 struct XdgGlobHashNode
53 {
54   xdg_unichar_t character;
55   const char *mime_type;
56   int weight;
57   int case_sensitive;
58   XdgGlobHashNode *next;
59   XdgGlobHashNode *child;
60 };
61 struct XdgGlobList
62 {
63   const char *data;
64   const char *mime_type;
65   int weight;
66   int case_sensitive;
67   XdgGlobList *next;
68 };
69
70 struct XdgGlobHash
71 {
72   XdgGlobList *literal_list;
73   XdgGlobHashNode *simple_node;
74   XdgGlobList *full_list;
75 };
76
77
78 /* XdgGlobList
79  */
80 static XdgGlobList *
81 _xdg_glob_list_new (void)
82 {
83   XdgGlobList *new_element;
84
85   new_element = calloc (1, sizeof (XdgGlobList));
86
87   return new_element;
88 }
89
90 /* Frees glob_list and all of it's children */
91 static void
92 _xdg_glob_list_free (XdgGlobList *glob_list)
93 {
94   XdgGlobList *ptr, *next;
95
96   ptr = glob_list;
97
98   while (ptr != NULL)
99     {
100       next = ptr->next;
101
102       if (ptr->data)
103         free ((void *) ptr->data);
104       if (ptr->mime_type)
105         free ((void *) ptr->mime_type);
106       free (ptr);
107
108       ptr = next;
109     }
110 }
111
112 static XdgGlobList *
113 _xdg_glob_list_append (XdgGlobList *glob_list,
114                        void        *data,
115                        const char  *mime_type,
116                        int          weight,
117                        int          case_sensitive)
118 {
119   XdgGlobList *new_element;
120   XdgGlobList *tmp_element;
121
122   tmp_element = glob_list;
123   while (tmp_element != NULL)
124     {
125       if (strcmp (tmp_element->data, data) == 0 &&
126           strcmp (tmp_element->mime_type, mime_type) == 0)
127         return glob_list;
128
129       tmp_element = tmp_element->next;
130     }
131
132   new_element = _xdg_glob_list_new ();
133   new_element->data = data;
134   new_element->mime_type = mime_type;
135   new_element->weight = weight;
136   new_element->case_sensitive = case_sensitive;
137   if (glob_list == NULL)
138     return new_element;
139
140   tmp_element = glob_list;
141   while (tmp_element->next != NULL)
142     tmp_element = tmp_element->next;
143
144   tmp_element->next = new_element;
145
146   return glob_list;
147 }
148
149 /* XdgGlobHashNode
150  */
151
152 static XdgGlobHashNode *
153 _xdg_glob_hash_node_new (void)
154 {
155   XdgGlobHashNode *glob_hash_node;
156
157   glob_hash_node = calloc (1, sizeof (XdgGlobHashNode));
158
159   return glob_hash_node;
160 }
161
162 static void
163 _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node,
164                           int depth)
165 {
166   int i;
167   for (i = 0; i < depth; i++)
168     printf (" ");
169
170   printf ("%c", (char)glob_hash_node->character);
171   if (glob_hash_node->mime_type)
172     printf (" - %s %d\n", glob_hash_node->mime_type, glob_hash_node->weight);
173   else
174     printf ("\n");
175   if (glob_hash_node->child)
176     _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1);
177   if (glob_hash_node->next)
178     _xdg_glob_hash_node_dump (glob_hash_node->next, depth);
179 }
180
181 static XdgGlobHashNode *
182 _xdg_glob_hash_insert_ucs4 (XdgGlobHashNode *glob_hash_node,
183                             xdg_unichar_t   *text,
184                             const char      *mime_type,
185                             int              weight,
186                             int              case_sensitive)
187 {
188   XdgGlobHashNode *node;
189   xdg_unichar_t character;
190
191   character = text[0];
192
193   if ((glob_hash_node == NULL) ||
194       (character < glob_hash_node->character))
195     {
196       node = _xdg_glob_hash_node_new ();
197       node->character = character;
198       node->next = glob_hash_node;
199       glob_hash_node = node;
200     }
201   else if (character == glob_hash_node->character)
202     {
203       node = glob_hash_node;
204     }
205   else
206     {
207       XdgGlobHashNode *prev_node;
208       int found_node = FALSE;
209
210       /* Look for the first character of text in glob_hash_node, and insert it if we
211        * have to.*/
212       prev_node = glob_hash_node;
213       node = prev_node->next;
214
215       while (node != NULL)
216         {
217           if (character < node->character)
218             {
219               node = _xdg_glob_hash_node_new ();
220               node->character = character;
221               node->next = prev_node->next;
222               prev_node->next = node;
223
224               found_node = TRUE;
225               break;
226             }
227           else if (character == node->character)
228             {
229               found_node = TRUE;
230               break;
231             }
232           prev_node = node;
233           node = node->next;
234         }
235
236       if (! found_node)
237         {
238           node = _xdg_glob_hash_node_new ();
239           node->character = character;
240           node->next = prev_node->next;
241           prev_node->next = node;
242         }
243     }
244
245   text++;
246   if (*text == 0)
247     {
248       if (node->mime_type)
249         {
250           if (strcmp (node->mime_type, mime_type) != 0)
251             {
252               XdgGlobHashNode *child;
253               int found_node = FALSE;
254
255               child = node->child;
256               while (child && child->character == 0)
257                 {
258                   if (strcmp (child->mime_type, mime_type) == 0)
259                     {
260                       found_node = TRUE;
261                       break;
262                     }
263                   child = child->next;
264                 }
265
266               if (!found_node)
267                 {
268                   child = _xdg_glob_hash_node_new ();
269                   child->character = 0;
270                   child->mime_type = strdup (mime_type);
271                   child->weight = weight;
272                   child->case_sensitive = case_sensitive;
273                   child->child = NULL;
274                   child->next = node->child;
275                   node->child = child;
276                 }
277             }
278         }
279       else
280         {
281           node->mime_type = strdup (mime_type);
282           node->weight = weight;
283           node->case_sensitive = case_sensitive;
284         }
285     }
286   else
287     {
288       node->child = _xdg_glob_hash_insert_ucs4 (node->child, text, mime_type, weight, case_sensitive);
289     }
290   return glob_hash_node;
291 }
292
293 /* glob must be valid UTF-8 */
294 static XdgGlobHashNode *
295 _xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node,
296                             const char      *text,
297                             const char      *mime_type,
298                             int              weight,
299                             int              case_sensitive)
300 {
301   XdgGlobHashNode *node;
302   xdg_unichar_t *unitext;
303   int len;
304
305   unitext = _xdg_convert_to_ucs4 (text, &len);
306   _xdg_reverse_ucs4 (unitext, len);
307   node = _xdg_glob_hash_insert_ucs4 (glob_hash_node, unitext, mime_type, weight, case_sensitive);
308   free (unitext);
309   return node;
310 }
311
312 typedef struct {
313   const char *mime;
314   int weight;
315 } MimeWeight;
316
317 static int
318 _xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node,
319                                       const char      *file_name,
320                                       int              len,
321                                       int              case_sensitive_check,
322                                       MimeWeight       mime_types[],
323                                       int              n_mime_types)
324 {
325   int n;
326   XdgGlobHashNode *node;
327   xdg_unichar_t character;
328
329   if (glob_hash_node == NULL)
330     return 0;
331
332   character = file_name[len - 1];
333
334   for (node = glob_hash_node; node && character >= node->character; node = node->next)
335     {
336       if (character == node->character)
337         {
338           len--;
339           n = 0;
340           if (len > 0) 
341             {
342               n = _xdg_glob_hash_node_lookup_file_name (node->child,
343                                                         file_name,
344                                                         len,
345                                                         case_sensitive_check,
346                                                         mime_types,
347                                                         n_mime_types);
348             }
349           if (n == 0)
350             {
351               if (node->mime_type &&
352                   (case_sensitive_check ||
353                    !node->case_sensitive))
354                 {
355                   mime_types[n].mime = node->mime_type;
356                   mime_types[n].weight = node->weight;
357                   n++; 
358                 }
359               node = node->child;
360               while (n < n_mime_types && node && node->character == 0)
361                 {
362                   if (node->mime_type &&
363                       (case_sensitive_check ||
364                        !node->case_sensitive))
365                     {
366                       mime_types[n].mime = node->mime_type;
367                       mime_types[n].weight = node->weight;
368                       n++;
369                     }
370                   node = node->next;
371                 }
372             }
373           return n;
374         }
375     }
376
377   return 0;
378 }
379
380 static int compare_mime_weight (const void *a, const void *b)
381 {
382   const MimeWeight *aa = (const MimeWeight *)a;
383   const MimeWeight *bb = (const MimeWeight *)b;
384
385   return aa->weight - bb->weight;
386 }
387
388 #define ISUPPER(c)              ((c) >= 'A' && (c) <= 'Z')
389 static char *
390 ascii_tolower (const char *str)
391 {
392   char *p, *lower;
393
394   lower = strdup (str);
395   p = lower;
396   while (*p != 0)
397     {
398       char c = *p;
399       *p++ = ISUPPER (c) ? c - 'A' + 'a' : c;
400     }
401   return lower;
402 }
403
404 int
405 _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash,
406                                  const char  *file_name,
407                                  const char  *mime_types[],
408                                  int          n_mime_types)
409 {
410   XdgGlobList *list;
411   int i, n;
412   MimeWeight mimes[10];
413   int n_mimes = 10;
414   int len;
415   char *lower_case;
416   int try_lower_case;
417
418   /* First, check the literals */
419
420   assert (file_name != NULL && n_mime_types > 0);
421
422   n = 0;
423
424   lower_case = ascii_tolower (file_name);
425
426   for (list = glob_hash->literal_list; list; list = list->next)
427     {
428       if (strcmp ((const char *)list->data, file_name) == 0)
429         {
430           mime_types[0] = list->mime_type;
431           free (lower_case);
432           return 1;
433         }
434     }
435
436   for (list = glob_hash->literal_list; list; list = list->next)
437     {
438       if (!list->case_sensitive &&
439           strcmp ((const char *)list->data, lower_case) == 0)
440         {
441           mime_types[0] = list->mime_type;
442           free (lower_case);
443           return 1;
444         }
445     }
446
447
448   len = strlen (file_name);
449   n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, lower_case, len, FALSE,
450                                             mimes, n_mimes);
451   if (n == 0)
452     n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, file_name, len, TRUE,
453                                               mimes, n_mimes);
454
455   if (n == 0)
456     {
457       for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next)
458         {
459           if (fnmatch ((const char *)list->data, file_name, 0) == 0)
460             {
461               mimes[n].mime = list->mime_type;
462               mimes[n].weight = list->weight;
463               n++;
464             }
465         }
466     }
467   free (lower_case);
468
469   qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight);
470
471   if (n_mime_types < n)
472     n = n_mime_types;
473
474   for (i = 0; i < n; i++)
475     mime_types[i] = mimes[i].mime;
476
477   return n;
478 }
479
480
481
482 /* XdgGlobHash
483  */
484
485 XdgGlobHash *
486 _xdg_glob_hash_new (void)
487 {
488   XdgGlobHash *glob_hash;
489
490   glob_hash = calloc (1, sizeof (XdgGlobHash));
491
492   return glob_hash;
493 }
494
495
496 static void
497 _xdg_glob_hash_free_nodes (XdgGlobHashNode *node)
498 {
499   if (node)
500     {
501       if (node->child)
502        _xdg_glob_hash_free_nodes (node->child);
503       if (node->next)
504        _xdg_glob_hash_free_nodes (node->next);
505       if (node->mime_type)
506         free ((void *) node->mime_type);
507       free (node);
508     }
509 }
510
511 void
512 _xdg_glob_hash_free (XdgGlobHash *glob_hash)
513 {
514   _xdg_glob_list_free (glob_hash->literal_list);
515   _xdg_glob_list_free (glob_hash->full_list);
516   _xdg_glob_hash_free_nodes (glob_hash->simple_node);
517   free (glob_hash);
518 }
519
520 XdgGlobType
521 _xdg_glob_determine_type (const char *glob)
522 {
523   const char *ptr;
524   int maybe_in_simple_glob = FALSE;
525   int first_char = TRUE;
526
527   ptr = glob;
528
529   while (*ptr != '\0')
530     {
531       if (*ptr == '*' && first_char)
532         maybe_in_simple_glob = TRUE;
533       else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*')
534           return XDG_GLOB_FULL;
535
536       first_char = FALSE;
537       ptr = _xdg_utf8_next_char (ptr);
538     }
539   if (maybe_in_simple_glob)
540     return XDG_GLOB_SIMPLE;
541   else
542     return XDG_GLOB_LITERAL;
543 }
544
545 /* glob must be valid UTF-8 */
546 void
547 _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash,
548                             const char  *glob,
549                             const char  *mime_type,
550                             int          weight,
551                             int          case_sensitive)
552 {
553   XdgGlobType type;
554
555   assert (glob_hash != NULL);
556   assert (glob != NULL);
557
558   type = _xdg_glob_determine_type (glob);
559
560   switch (type)
561     {
562     case XDG_GLOB_LITERAL:
563       glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type), weight, case_sensitive);
564       break;
565     case XDG_GLOB_SIMPLE:
566       glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type, weight, case_sensitive);
567       break;
568     case XDG_GLOB_FULL:
569       glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type), weight, case_sensitive);
570       break;
571     }
572 }
573
574 void
575 _xdg_glob_hash_dump (XdgGlobHash *glob_hash)
576 {
577   XdgGlobList *list;
578   printf ("LITERAL STRINGS\n");
579   if (!glob_hash || glob_hash->literal_list == NULL)
580     {
581       printf ("    None\n");
582     }
583   else
584     {
585       for (list = glob_hash->literal_list; list; list = list->next)
586         printf ("    %s - %s %d\n", (char *)list->data, list->mime_type, list->weight);
587     }
588   printf ("\nSIMPLE GLOBS\n");
589   if (!glob_hash || glob_hash->simple_node == NULL)
590     {
591       printf ("    None\n");
592     }
593   else
594     {
595       _xdg_glob_hash_node_dump (glob_hash->simple_node, 4);
596     }
597
598   printf ("\nFULL GLOBS\n");
599   if (!glob_hash || glob_hash->full_list == NULL)
600     {
601       printf ("    None\n");
602     }
603   else
604     {
605       for (list = glob_hash->full_list; list; list = list->next)
606         printf ("    %s - %s %d\n", (char *)list->data, list->mime_type, list->weight);
607     }
608 }
609
610
611 void
612 _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash,
613                                const char  *file_name,
614                                int          version_two)
615 {
616   FILE *glob_file;
617   char line[255];
618   char *p;
619
620   glob_file = fopen (file_name, "r");
621
622   if (glob_file == NULL)
623     return;
624
625   /* FIXME: Not UTF-8 safe.  Doesn't work if lines are greater than 255 chars.
626    * Blah */
627   while (fgets (line, 255, glob_file) != NULL)
628     {
629       char *colon;
630       char *mimetype, *glob, *end;
631       int weight;
632       int case_sensitive;
633
634       if (line[0] == '#' || line[0] == 0)
635         continue;
636
637       end = line + strlen(line) - 1;
638       if (*end == '\n')
639         *end = 0;
640
641       p = line;
642       if (version_two)
643         {
644           colon = strchr (p, ':');
645           if (colon == NULL)
646             continue;
647           *colon = 0;
648           weight = atoi (p);
649           p = colon + 1;
650         }
651       else
652         weight = 50;
653
654       colon = strchr (p, ':');
655       if (colon == NULL)
656         continue;
657       *colon = 0;
658
659       mimetype = p;
660       p = colon + 1;
661       glob = p;
662       case_sensitive = FALSE;
663
664       colon = strchr (p, ':');
665       if (version_two && colon != NULL)
666         {
667           char *flag;
668
669           /* We got flags */
670           *colon = 0;
671           p = colon + 1;
672
673           /* Flags end at next colon */
674           colon = strchr (p, ':');
675           if (colon != NULL)
676             *colon = 0;
677
678           flag = strstr (p, "cs");
679           if (flag != NULL &&
680               /* Start or after comma */
681               (flag == p ||
682                flag[-1] == ',') &&
683               /* ends with comma or end of string */
684               (flag[2] == 0 ||
685                flag[2] == ','))
686             case_sensitive = TRUE;
687         }
688
689       _xdg_glob_hash_append_glob (glob_hash, glob, mimetype, weight, case_sensitive);
690     }
691
692   fclose (glob_file);
693 }