Initial commit
[platform/upstream/glib2.0.git] / gio / xdgmime / xdgmimecache.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmimealias.c: Private file.  mmappable caches for mime data
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  *
6  * Copyright (C) 2005  Matthias Clasen <mclasen@redhat.com>
7  *
8  * Licensed under the Academic Free License version 2.0
9  * Or under the following terms:
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Lesser General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public
22  * License along with this library; if not, write to the
23  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24  * Boston, MA 02111-1307, USA.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 #include <fcntl.h>
36 #include <unistd.h>
37 #include <fnmatch.h>
38 #include <assert.h>
39
40 #include <netinet/in.h> /* for ntohl/ntohs */
41
42 #ifdef HAVE_MMAP
43 #include <sys/mman.h>
44 #else
45 #warning Building xdgmime without MMAP support. Binary "mime.info" cache files will not be used.
46 #endif
47
48 #include <sys/stat.h>
49 #include <sys/types.h>
50
51 #include "xdgmimecache.h"
52 #include "xdgmimeint.h"
53
54 #ifndef MAX
55 #define MAX(a,b) ((a) > (b) ? (a) : (b))
56 #endif
57
58 #ifndef FALSE
59 #define FALSE   (0)
60 #endif
61
62 #ifndef TRUE
63 #define TRUE    (!FALSE)
64 #endif
65
66 #ifndef _O_BINARY
67 #define _O_BINARY 0
68 #endif
69
70 #ifndef MAP_FAILED
71 #define MAP_FAILED ((void *) -1)
72 #endif
73
74 #define MAJOR_VERSION 1
75 #define MINOR_VERSION_MIN 1
76 #define MINOR_VERSION_MAX 2
77
78 struct _XdgMimeCache
79 {
80   int ref_count;
81   int minor;
82
83   size_t  size;
84   char   *buffer;
85 };
86
87 #define GET_UINT16(cache,offset) (ntohs(*(xdg_uint16_t*)((cache) + (offset))))
88 #define GET_UINT32(cache,offset) (ntohl(*(xdg_uint32_t*)((cache) + (offset))))
89
90 XdgMimeCache *
91 _xdg_mime_cache_ref (XdgMimeCache *cache)
92 {
93   cache->ref_count++;
94   return cache;
95 }
96
97 void
98 _xdg_mime_cache_unref (XdgMimeCache *cache)
99 {
100   cache->ref_count--;
101
102   if (cache->ref_count == 0)
103     {
104 #ifdef HAVE_MMAP
105       munmap (cache->buffer, cache->size);
106 #endif
107       free (cache);
108     }
109 }
110
111 XdgMimeCache *
112 _xdg_mime_cache_new_from_file (const char *file_name)
113 {
114   XdgMimeCache *cache = NULL;
115
116 #ifdef HAVE_MMAP
117   int fd = -1;
118   struct stat st;
119   char *buffer = NULL;
120   int minor;
121
122   /* Open the file and map it into memory */
123   fd = open (file_name, O_RDONLY|_O_BINARY, 0);
124
125   if (fd < 0)
126     return NULL;
127   
128   if (fstat (fd, &st) < 0 || st.st_size < 4)
129     goto done;
130
131   buffer = (char *) mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
132
133   if (buffer == MAP_FAILED)
134     goto done;
135
136   minor = GET_UINT16 (buffer, 2);
137   /* Verify version */
138   if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
139       (minor < MINOR_VERSION_MIN ||
140        minor > MINOR_VERSION_MAX))
141     {
142       munmap (buffer, st.st_size);
143
144       goto done;
145     }
146   
147   cache = (XdgMimeCache *) malloc (sizeof (XdgMimeCache));
148   cache->minor = minor;
149   cache->ref_count = 1;
150   cache->buffer = buffer;
151   cache->size = st.st_size;
152
153  done:
154   if (fd != -1)
155     close (fd);
156
157 #endif  /* HAVE_MMAP */
158
159   return cache;
160 }
161
162 static int
163 cache_magic_matchlet_compare_to_data (XdgMimeCache *cache, 
164                                       xdg_uint32_t  offset,
165                                       const void   *data,
166                                       size_t        len)
167 {
168   xdg_uint32_t range_start = GET_UINT32 (cache->buffer, offset);
169   xdg_uint32_t range_length = GET_UINT32 (cache->buffer, offset + 4);
170   xdg_uint32_t data_length = GET_UINT32 (cache->buffer, offset + 12);
171   xdg_uint32_t data_offset = GET_UINT32 (cache->buffer, offset + 16);
172   xdg_uint32_t mask_offset = GET_UINT32 (cache->buffer, offset + 20);
173   
174   int i, j;
175
176   for (i = range_start; i < range_start + range_length; i++)
177     {
178       int valid_matchlet = TRUE;
179       
180       if (i + data_length > len)
181         return FALSE;
182
183       if (mask_offset)
184         {
185           for (j = 0; j < data_length; j++)
186             {
187               if ((((unsigned char *)cache->buffer)[data_offset + j] & ((unsigned char *)cache->buffer)[mask_offset + j]) !=
188                   ((((unsigned char *) data)[j + i]) & ((unsigned char *)cache->buffer)[mask_offset + j]))
189                 {
190                   valid_matchlet = FALSE;
191                   break;
192                 }
193             }
194         }
195       else
196         {
197           for (j = 0; j < data_length; j++)
198             {
199               if (((unsigned char *)cache->buffer)[data_offset + j] != ((unsigned char *) data)[j + i])
200                 {
201                   valid_matchlet = FALSE;
202                   break;
203                 }
204             }
205         }
206       
207       if (valid_matchlet)
208         return TRUE;
209     }
210   
211   return FALSE;  
212 }
213
214 static int
215 cache_magic_matchlet_compare (XdgMimeCache *cache, 
216                               xdg_uint32_t  offset,
217                               const void   *data,
218                               size_t        len)
219 {
220   xdg_uint32_t n_children = GET_UINT32 (cache->buffer, offset + 24);
221   xdg_uint32_t child_offset = GET_UINT32 (cache->buffer, offset + 28);
222
223   int i;
224   
225   if (cache_magic_matchlet_compare_to_data (cache, offset, data, len))
226     {
227       if (n_children == 0)
228         return TRUE;
229       
230       for (i = 0; i < n_children; i++)
231         {
232           if (cache_magic_matchlet_compare (cache, child_offset + 32 * i,
233                                             data, len))
234             return TRUE;
235         }
236     }
237   
238   return FALSE;  
239 }
240
241 static const char *
242 cache_magic_compare_to_data (XdgMimeCache *cache, 
243                              xdg_uint32_t  offset,
244                              const void   *data, 
245                              size_t        len, 
246                              int          *prio)
247 {
248   xdg_uint32_t priority = GET_UINT32 (cache->buffer, offset);
249   xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, offset + 4);
250   xdg_uint32_t n_matchlets = GET_UINT32 (cache->buffer, offset + 8);
251   xdg_uint32_t matchlet_offset = GET_UINT32 (cache->buffer, offset + 12);
252
253   int i;
254
255   for (i = 0; i < n_matchlets; i++)
256     {
257       if (cache_magic_matchlet_compare (cache, matchlet_offset + i * 32, 
258                                         data, len))
259         {
260           *prio = priority;
261           
262           return cache->buffer + mimetype_offset;
263         }
264     }
265
266   return NULL;
267 }
268
269 static const char *
270 cache_magic_lookup_data (XdgMimeCache *cache, 
271                          const void   *data, 
272                          size_t        len, 
273                          int          *prio,
274                          const char   *mime_types[],
275                          int           n_mime_types)
276 {
277   xdg_uint32_t list_offset;
278   xdg_uint32_t n_entries;
279   xdg_uint32_t offset;
280
281   int j, n;
282
283   *prio = 0;
284
285   list_offset = GET_UINT32 (cache->buffer, 24);
286   n_entries = GET_UINT32 (cache->buffer, list_offset);
287   offset = GET_UINT32 (cache->buffer, list_offset + 8);
288   
289   for (j = 0; j < n_entries; j++)
290     {
291       const char *match;
292
293       match = cache_magic_compare_to_data (cache, offset + 16 * j, 
294                                            data, len, prio);
295       if (match)
296         return match;
297       else
298         {
299           xdg_uint32_t mimetype_offset;
300           const char *non_match;
301           
302           mimetype_offset = GET_UINT32 (cache->buffer, offset + 16 * j + 4);
303           non_match = cache->buffer + mimetype_offset;
304
305           for (n = 0; n < n_mime_types; n++)
306             {
307               if (mime_types[n] && 
308                   _xdg_mime_mime_type_equal (mime_types[n], non_match))
309                 mime_types[n] = NULL;
310             }
311         }
312     }
313
314   return NULL;
315 }
316
317 static const char *
318 cache_alias_lookup (const char *alias)
319 {
320   const char *ptr;
321   int i, min, max, mid, cmp;
322
323   for (i = 0; _caches[i]; i++)
324     {
325       XdgMimeCache *cache = _caches[i];
326       xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 4);
327       xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset);
328       xdg_uint32_t offset;
329
330       min = 0; 
331       max = n_entries - 1;
332       while (max >= min) 
333         {
334           mid = (min + max) / 2;
335
336           offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid);
337           ptr = cache->buffer + offset;
338           cmp = strcmp (ptr, alias);
339           
340           if (cmp < 0)
341             min = mid + 1;
342           else if (cmp > 0)
343             max = mid - 1;
344           else
345             {
346               offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4);
347               return cache->buffer + offset;
348             }
349         }
350     }
351
352   return NULL;
353 }
354
355 typedef struct {
356   const char *mime;
357   int weight;
358 } MimeWeight;
359
360 static int
361 cache_glob_lookup_literal (const char *file_name,
362                            const char *mime_types[],
363                            int         n_mime_types,
364                            int         case_sensitive_check)
365 {
366   const char *ptr;
367   int i, min, max, mid, cmp;
368
369   for (i = 0; _caches[i]; i++)
370     {
371       XdgMimeCache *cache = _caches[i];
372       xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 12);
373       xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset);
374       xdg_uint32_t offset;
375
376       min = 0; 
377       max = n_entries - 1;
378       while (max >= min) 
379         {
380           mid = (min + max) / 2;
381
382           offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid);
383           ptr = cache->buffer + offset;
384           cmp = strcmp (ptr, file_name);
385
386           if (cmp < 0)
387             min = mid + 1;
388           else if (cmp > 0)
389             max = mid - 1;
390           else
391             {
392               int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 8);
393               int case_sensitive = weight & 0x100;
394               weight = weight & 0xff;
395
396               if (case_sensitive_check || !case_sensitive)
397                 {
398                   offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 4);
399                   mime_types[0] = (const char *)(cache->buffer + offset);
400
401                   return 1;
402                 }
403               return 0;
404             }
405         }
406     }
407
408   return 0;
409 }
410
411 static int
412 cache_glob_lookup_fnmatch (const char *file_name,
413                            MimeWeight  mime_types[],
414                            int         n_mime_types)
415 {
416   const char *mime_type;
417   const char *ptr;
418
419   int i, j, n;
420
421   n = 0;
422   for (i = 0; _caches[i]; i++)
423     {
424       XdgMimeCache *cache = _caches[i];
425
426       xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 20);
427       xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset);
428
429       for (j = 0; j < n_entries && n < n_mime_types; j++)
430         {
431           xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j);
432           xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 4);
433           int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 8);
434           weight = weight & 0xff;
435           ptr = cache->buffer + offset;
436           mime_type = cache->buffer + mimetype_offset;
437
438           /* FIXME: Not UTF-8 safe */
439           if (fnmatch (ptr, file_name, 0) == 0)
440             {
441               mime_types[n].mime = mime_type;
442               mime_types[n].weight = weight;
443               n++;
444             }
445         }
446
447       if (n == n_mime_types)
448         break;
449     }
450
451   return n;
452 }
453
454 static int
455 cache_glob_node_lookup_suffix (XdgMimeCache  *cache,
456                                xdg_uint32_t   n_entries,
457                                xdg_uint32_t   offset,
458                                const char    *file_name,
459                                int            len,
460                                int            case_sensitive_check,
461                                MimeWeight     mime_types[],
462                                int            n_mime_types)
463 {
464   xdg_unichar_t character;
465   xdg_unichar_t match_char;
466   xdg_uint32_t mimetype_offset;
467   xdg_uint32_t n_children;
468   xdg_uint32_t child_offset; 
469   int weight;
470   int case_sensitive;
471
472   int min, max, mid, n, i;
473
474   character = file_name[len - 1];
475
476   assert (character != 0);
477
478   min = 0;
479   max = n_entries - 1;
480   while (max >= min)
481     {
482       mid = (min + max) /  2;
483       match_char = GET_UINT32 (cache->buffer, offset + 12 * mid);
484       if (match_char < character)
485         min = mid + 1;
486       else if (match_char > character)
487         max = mid - 1;
488       else 
489         {
490           len--;
491           n = 0;
492           n_children = GET_UINT32 (cache->buffer, offset + 12 * mid + 4);
493           child_offset = GET_UINT32 (cache->buffer, offset + 12 * mid + 8);
494       
495           if (len > 0)
496             {
497               n = cache_glob_node_lookup_suffix (cache, 
498                                                  n_children, child_offset,
499                                                  file_name, len, 
500                                                  case_sensitive_check,
501                                                  mime_types,
502                                                  n_mime_types);
503             }
504           if (n == 0)
505             {
506               i = 0;
507               while (n < n_mime_types && i < n_children)
508                 {
509                   match_char = GET_UINT32 (cache->buffer, child_offset + 12 * i);
510                   if (match_char != 0)
511                     break;
512
513                   mimetype_offset = GET_UINT32 (cache->buffer, child_offset + 12 * i + 4);
514                   weight = GET_UINT32 (cache->buffer, child_offset + 12 * i + 8);
515                   case_sensitive = weight & 0x100;
516                   weight = weight & 0xff;
517
518                   if (case_sensitive_check || !case_sensitive)
519                     {
520                       mime_types[n].mime = cache->buffer + mimetype_offset;
521                       mime_types[n].weight = weight;
522                       n++;
523                     }
524                   i++;
525                 }
526             }
527           return n;
528         }
529     }
530   return 0;
531 }
532
533 static int
534 cache_glob_lookup_suffix (const char *file_name,
535                           int         len,
536                           int         ignore_case,
537                           MimeWeight  mime_types[],
538                           int         n_mime_types)
539 {
540   int i, n;
541
542   n = 0;
543   for (i = 0; _caches[i]; i++)
544     {
545       XdgMimeCache *cache = _caches[i];
546
547       xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 16);
548       xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset);
549       xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4);
550
551       n += cache_glob_node_lookup_suffix (cache, 
552                                           n_entries, offset, 
553                                           file_name, len,
554                                           ignore_case,
555                                           mime_types + n,
556                                           n_mime_types - n);
557       if (n == n_mime_types)
558         break;
559     }
560
561   return n;
562 }
563
564 static int compare_mime_weight (const void *a, const void *b)
565 {
566   const MimeWeight *aa = (const MimeWeight *)a;
567   const MimeWeight *bb = (const MimeWeight *)b;
568
569   return bb->weight - aa->weight;
570 }
571
572 #define ISUPPER(c)              ((c) >= 'A' && (c) <= 'Z')
573 static char *
574 ascii_tolower (const char *str)
575 {
576   char *p, *lower;
577
578   lower = strdup (str);
579   p = lower;
580   while (*p != 0)
581     {
582       char c = *p;
583       *p++ = ISUPPER (c) ? c - 'A' + 'a' : c;
584     }
585   return lower;
586 }
587
588 static int
589 filter_out_dupes (MimeWeight mimes[], int n_mimes)
590 {
591   int last;
592   int i, j;
593
594   last = n_mimes;
595
596   for (i = 0; i < last; i++)
597     {
598       j = i + 1;
599       while (j < last)
600         {
601           if (strcmp (mimes[i].mime, mimes[j].mime) == 0)
602             {
603               mimes[i].weight = MAX (mimes[i].weight, mimes[j].weight);
604               last--;
605               mimes[j].mime = mimes[last].mime;
606               mimes[j].weight = mimes[last].weight;
607             }
608           else
609             j++;
610         }
611     }
612
613   return last;
614 }
615
616 static int
617 cache_glob_lookup_file_name (const char *file_name,
618                              const char *mime_types[],
619                              int         n_mime_types)
620 {
621   int n;
622   MimeWeight mimes[10];
623   int n_mimes = 10;
624   int i;
625   int len;
626   char *lower_case;
627
628   assert (file_name != NULL && n_mime_types > 0);
629
630   /* First, check the literals */
631
632   lower_case = ascii_tolower (file_name);
633
634   n = cache_glob_lookup_literal (lower_case, mime_types, n_mime_types, FALSE);
635   if (n > 0)
636     {
637       free (lower_case);
638       return n;
639     }
640
641   n = cache_glob_lookup_literal (file_name, mime_types, n_mime_types, TRUE);
642   if (n > 0)
643     {
644       free (lower_case);
645       return n;
646     }
647
648   len = strlen (file_name);
649   n = cache_glob_lookup_suffix (lower_case, len, FALSE, mimes, n_mimes);
650   if (n < 2)
651     n += cache_glob_lookup_suffix (file_name, len, TRUE, mimes + n, n_mimes - n);
652
653   free (lower_case);
654
655   /* Last, try fnmatch */
656   if (n < 2)
657     n += cache_glob_lookup_fnmatch (file_name, mimes + n, n_mimes - n);
658
659   n = filter_out_dupes (mimes, n);
660
661   qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight);
662
663   if (n_mime_types < n)
664     n = n_mime_types;
665
666   for (i = 0; i < n; i++)
667     mime_types[i] = mimes[i].mime;
668
669   return n;
670 }
671
672 int
673 _xdg_mime_cache_get_max_buffer_extents (void)
674 {
675   xdg_uint32_t offset;
676   xdg_uint32_t max_extent;
677   int i;
678
679   max_extent = 0;
680   for (i = 0; _caches[i]; i++)
681     {
682       XdgMimeCache *cache = _caches[i];
683
684       offset = GET_UINT32 (cache->buffer, 24);
685       max_extent = MAX (max_extent, GET_UINT32 (cache->buffer, offset + 4));
686     }
687
688   return max_extent;
689 }
690
691 static const char *
692 cache_get_mime_type_for_data (const void *data,
693                               size_t      len,
694                               int        *result_prio,
695                               const char *mime_types[],
696                               int         n_mime_types)
697 {
698   const char *mime_type;
699   int i, n, priority;
700
701   priority = 0;
702   mime_type = NULL;
703   for (i = 0; _caches[i]; i++)
704     {
705       XdgMimeCache *cache = _caches[i];
706
707       int prio;
708       const char *match;
709
710       match = cache_magic_lookup_data (cache, data, len, &prio, 
711                                        mime_types, n_mime_types);
712       if (prio > priority)
713         {
714           priority = prio;
715           mime_type = match;
716         }
717     }
718
719   if (result_prio)
720     *result_prio = priority;
721   
722   if (priority > 0)
723     return mime_type;
724
725   for (n = 0; n < n_mime_types; n++)
726     {
727       
728       if (mime_types[n])
729         return mime_types[n];
730     }
731
732   return XDG_MIME_TYPE_UNKNOWN;
733 }
734
735 const char *
736 _xdg_mime_cache_get_mime_type_for_data (const void *data,
737                                         size_t      len,
738                                         int        *result_prio)
739 {
740   return cache_get_mime_type_for_data (data, len, result_prio, NULL, 0);
741 }
742
743 const char *
744 _xdg_mime_cache_get_mime_type_for_file (const char  *file_name,
745                                         struct stat *statbuf)
746 {
747   const char *mime_type;
748   const char *mime_types[10];
749   FILE *file;
750   unsigned char *data;
751   int max_extent;
752   int bytes_read;
753   struct stat buf;
754   const char *base_name;
755   int n;
756
757   if (file_name == NULL)
758     return NULL;
759
760   if (! _xdg_utf8_validate (file_name))
761     return NULL;
762
763   base_name = _xdg_get_base_name (file_name);
764   n = cache_glob_lookup_file_name (base_name, mime_types, 10);
765
766   if (n == 1)
767     return mime_types[0];
768
769   if (!statbuf)
770     {
771       if (stat (file_name, &buf) != 0)
772         return XDG_MIME_TYPE_UNKNOWN;
773
774       statbuf = &buf;
775     }
776
777   if (!S_ISREG (statbuf->st_mode))
778     return XDG_MIME_TYPE_UNKNOWN;
779
780   /* FIXME: Need to make sure that max_extent isn't totally broken.  This could
781    * be large and need getting from a stream instead of just reading it all
782    * in. */
783   max_extent = _xdg_mime_cache_get_max_buffer_extents ();
784   data = malloc (max_extent);
785   if (data == NULL)
786     return XDG_MIME_TYPE_UNKNOWN;
787         
788   file = fopen (file_name, "r");
789   if (file == NULL)
790     {
791       free (data);
792       return XDG_MIME_TYPE_UNKNOWN;
793     }
794
795   bytes_read = fread (data, 1, max_extent, file);
796   if (ferror (file))
797     {
798       free (data);
799       fclose (file);
800       return XDG_MIME_TYPE_UNKNOWN;
801     }
802
803   mime_type = cache_get_mime_type_for_data (data, bytes_read, NULL,
804                                             mime_types, n);
805
806   free (data);
807   fclose (file);
808
809   return mime_type;
810 }
811
812 const char *
813 _xdg_mime_cache_get_mime_type_from_file_name (const char *file_name)
814 {
815   const char *mime_type;
816
817   if (cache_glob_lookup_file_name (file_name, &mime_type, 1))
818     return mime_type;
819   else
820     return XDG_MIME_TYPE_UNKNOWN;
821 }
822
823 int
824 _xdg_mime_cache_get_mime_types_from_file_name (const char *file_name,
825                                                const char  *mime_types[],
826                                                int          n_mime_types)
827 {
828   return cache_glob_lookup_file_name (file_name, mime_types, n_mime_types);
829 }
830
831 #if 1
832 static int
833 is_super_type (const char *mime)
834 {
835   int length;
836   const char *type;
837
838   length = strlen (mime);
839   type = &(mime[length - 2]);
840
841   if (strcmp (type, "/*") == 0)
842     return 1;
843
844   return 0;
845 }
846 #endif
847
848 int
849 _xdg_mime_cache_mime_type_subclass (const char *mime,
850                                     const char *base)
851 {
852   const char *umime, *ubase;
853
854   int i, j, min, max, med, cmp;
855   
856   umime = _xdg_mime_cache_unalias_mime_type (mime);
857   ubase = _xdg_mime_cache_unalias_mime_type (base);
858
859   if (strcmp (umime, ubase) == 0)
860     return 1;
861
862   /* We really want to handle text/ * in GtkFileFilter, so we just
863    * turn on the supertype matching
864    */
865 #if 1
866   /* Handle supertypes */
867   if (is_super_type (ubase) &&
868       xdg_mime_media_type_equal (umime, ubase))
869     return 1;
870 #endif
871
872   /*  Handle special cases text/plain and application/octet-stream */
873   if (strcmp (ubase, "text/plain") == 0 && 
874       strncmp (umime, "text/", 5) == 0)
875     return 1;
876
877   if (strcmp (ubase, "application/octet-stream") == 0)
878     return 1;
879  
880   for (i = 0; _caches[i]; i++)
881     {
882       XdgMimeCache *cache = _caches[i];
883       
884       xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 8);
885       xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset);
886       xdg_uint32_t offset, n_parents, parent_offset;
887
888       min = 0; 
889       max = n_entries - 1;
890       while (max >= min)
891         {
892           med = (min + max)/2;
893           
894           offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med);
895           cmp = strcmp (cache->buffer + offset, umime);
896           if (cmp < 0)
897             min = med + 1;
898           else if (cmp > 0)
899             max = med - 1;
900           else
901             {
902               offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med + 4);
903               n_parents = GET_UINT32 (cache->buffer, offset);
904               
905               for (j = 0; j < n_parents; j++)
906                 {
907                   parent_offset = GET_UINT32 (cache->buffer, offset + 4 + 4 * j);
908                   if (_xdg_mime_cache_mime_type_subclass (cache->buffer + parent_offset, ubase))
909                     return 1;
910                 }
911
912               break;
913             }
914         }
915     }
916
917   return 0;
918 }
919
920 const char *
921 _xdg_mime_cache_unalias_mime_type (const char *mime)
922 {
923   const char *lookup;
924   
925   lookup = cache_alias_lookup (mime);
926   
927   if (lookup)
928     return lookup;
929   
930   return mime;  
931 }
932
933 char **
934 _xdg_mime_cache_list_mime_parents (const char *mime)
935 {
936   int i, j, k, l, p;
937   char *all_parents[128]; /* we'll stop at 128 */ 
938   char **result;
939
940   mime = xdg_mime_unalias_mime_type (mime);
941
942   p = 0;
943   for (i = 0; _caches[i]; i++)
944     {
945       XdgMimeCache *cache = _caches[i];
946   
947       xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, 8);
948       xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset);
949
950       for (j = 0; j < n_entries; j++)
951         {
952           xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j);
953           xdg_uint32_t parents_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j + 4);
954
955           if (strcmp (cache->buffer + mimetype_offset, mime) == 0)
956             {
957               xdg_uint32_t parent_mime_offset;
958               xdg_uint32_t n_parents = GET_UINT32 (cache->buffer, parents_offset);
959
960               for (k = 0; k < n_parents && p < 127; k++)
961                 {
962                   parent_mime_offset = GET_UINT32 (cache->buffer, parents_offset + 4 + 4 * k);
963
964                   /* Don't add same parent multiple times.
965                    * This can happen for instance if the same type is listed in multiple directories
966                    */
967                   for (l = 0; l < p; l++)
968                     {
969                       if (strcmp (all_parents[l], cache->buffer + parent_mime_offset) == 0)
970                         break;
971                     }
972
973                   if (l == p)
974                     all_parents[p++] = cache->buffer + parent_mime_offset;
975                 }
976
977               break;
978             }
979         }
980     }
981   all_parents[p++] = NULL;
982   
983   result = (char **) malloc (p * sizeof (char *));
984   memcpy (result, all_parents, p * sizeof (char *));
985
986   return result;
987 }
988
989 static const char *
990 cache_lookup_icon (const char *mime, int header)
991 {
992   const char *ptr;
993   int i, min, max, mid, cmp;
994
995   for (i = 0; _caches[i]; i++)
996     {
997       XdgMimeCache *cache = _caches[i];
998       xdg_uint32_t list_offset = GET_UINT32 (cache->buffer, header);
999       xdg_uint32_t n_entries = GET_UINT32 (cache->buffer, list_offset);
1000       xdg_uint32_t offset;
1001
1002       min = 0; 
1003       max = n_entries - 1;
1004       while (max >= min) 
1005         {
1006           mid = (min + max) / 2;
1007
1008           offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid);
1009           ptr = cache->buffer + offset;
1010           cmp = strcmp (ptr, mime);
1011          
1012           if (cmp < 0)
1013             min = mid + 1;
1014           else if (cmp > 0)
1015             max = mid - 1;
1016           else
1017             {
1018               offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4);
1019               return cache->buffer + offset;
1020             }
1021         }
1022     }
1023
1024   return NULL;
1025 }
1026
1027 const char *
1028 _xdg_mime_cache_get_generic_icon (const char *mime)
1029 {
1030   return cache_lookup_icon (mime, 36);
1031 }
1032
1033 const char *
1034 _xdg_mime_cache_get_icon (const char *mime)
1035 {
1036   return cache_lookup_icon (mime, 32);
1037 }
1038
1039 static void
1040 dump_glob_node (XdgMimeCache *cache,
1041                 xdg_uint32_t  offset,
1042                 int           depth)
1043 {
1044   xdg_unichar_t character;
1045   xdg_uint32_t mime_offset;
1046   xdg_uint32_t n_children;
1047   xdg_uint32_t child_offset;
1048   int i;
1049
1050   character = GET_UINT32 (cache->buffer, offset);
1051   mime_offset = GET_UINT32 (cache->buffer, offset + 4);
1052   n_children = GET_UINT32 (cache->buffer, offset + 8);
1053   child_offset = GET_UINT32 (cache->buffer, offset + 12);
1054   for (i = 0; i < depth; i++)
1055     printf (" ");
1056   printf ("%c", character);
1057   if (mime_offset)
1058     printf (" - %s", cache->buffer + mime_offset);
1059   printf ("\n");
1060   if (child_offset)
1061   {
1062     for (i = 0; i < n_children; i++)
1063       dump_glob_node (cache, child_offset + 20 * i, depth + 1);
1064   }
1065 }
1066
1067 void
1068 _xdg_mime_cache_glob_dump (void)
1069 {
1070   int i, j;
1071   for (i = 0; _caches[i]; i++)
1072   {
1073     XdgMimeCache *cache = _caches[i];
1074     xdg_uint32_t list_offset;
1075     xdg_uint32_t n_entries;
1076     xdg_uint32_t offset;
1077     list_offset = GET_UINT32 (cache->buffer, 16);
1078     n_entries = GET_UINT32 (cache->buffer, list_offset);
1079     offset = GET_UINT32 (cache->buffer, list_offset + 4);
1080     for (j = 0; j < n_entries; j++)
1081             dump_glob_node (cache, offset + 20 * j, 0);
1082   }
1083 }
1084
1085