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