1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmime.c: XDG Mime Spec mime resolver.  Based on version 0.11 of the spec.
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  *
6  * Copyright (C) 2003,2004  Red Hat, Inc.
7  * Copyright (C) 2003,2004  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 "xdgmime.h"
33 #include "xdgmimeint.h"
34 #include "xdgmimeglob.h"
35 #include "xdgmimemagic.h"
36 #include "xdgmimealias.h"
37 #include "xdgmimeicon.h"
38 #include "xdgmimeparent.h"
39 #include "xdgmimecache.h"
40 #include <stdio.h>
41 #include <string.h>
42 #include <sys/stat.h>
43 #include <sys/types.h>
44 #include <sys/time.h>
45 #include <unistd.h>
46 #include <assert.h>
47 
48 typedef struct XdgDirTimeList XdgDirTimeList;
49 typedef struct XdgCallbackList XdgCallbackList;
50 
51 static int need_reread = TRUE;
52 static time_t last_stat_time = 0;
53 
54 static XdgGlobHash *global_hash = NULL;
55 static XdgMimeMagic *global_magic = NULL;
56 static XdgAliasList *alias_list = NULL;
57 static XdgParentList *parent_list = NULL;
58 static XdgDirTimeList *dir_time_list = NULL;
59 static XdgCallbackList *callback_list = NULL;
60 static XdgIconList *icon_list = NULL;
61 static XdgIconList *generic_icon_list = NULL;
62 
63 XdgMimeCache **_caches = NULL;
64 static int n_caches = 0;
65 
66 const char xdg_mime_type_unknown[] = "application/octet-stream";
67 const char xdg_mime_type_empty[] = "application/x-zerosize";
68 const char xdg_mime_type_textplain[] = "text/plain";
69 
70 
71 enum
72 {
73   XDG_CHECKED_UNCHECKED,
74   XDG_CHECKED_VALID,
75   XDG_CHECKED_INVALID
76 };
77 
78 struct XdgDirTimeList
79 {
80   time_t mtime;
81   char *directory_name;
82   int checked;
83   XdgDirTimeList *next;
84 };
85 
86 struct XdgCallbackList
87 {
88   XdgCallbackList *next;
89   XdgCallbackList *prev;
90   int              callback_id;
91   XdgMimeCallback  callback;
92   void            *data;
93   XdgMimeDestroy   destroy;
94 };
95 
96 /* Function called by xdg_run_command_on_dirs.  If it returns TRUE, further
97  * directories aren't looked at */
98 typedef int (*XdgDirectoryFunc) (const char *directory,
99 				 void       *user_data);
100 
101 static void
xdg_dir_time_list_add(char * file_name,time_t mtime)102 xdg_dir_time_list_add (char   *file_name,
103 		       time_t  mtime)
104 {
105   XdgDirTimeList *list;
106 
107   for (list = dir_time_list; list; list = list->next)
108     {
109       if (strcmp (list->directory_name, file_name) == 0)
110         {
111           free (file_name);
112           return;
113         }
114     }
115 
116   list = calloc (1, sizeof (XdgDirTimeList));
117   list->checked = XDG_CHECKED_UNCHECKED;
118   list->directory_name = file_name;
119   list->mtime = mtime;
120   list->next = dir_time_list;
121   dir_time_list = list;
122 }
123 
124 static void
xdg_dir_time_list_free(XdgDirTimeList * list)125 xdg_dir_time_list_free (XdgDirTimeList *list)
126 {
127   XdgDirTimeList *next;
128 
129   while (list)
130     {
131       next = list->next;
132       free (list->directory_name);
133       free (list);
134       list = next;
135     }
136 }
137 
138 static int
xdg_mime_init_from_directory(const char * directory)139 xdg_mime_init_from_directory (const char *directory)
140 {
141   char *file_name;
142   struct stat st;
143 
144   assert (directory != NULL);
145 
146   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
147   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
148   if (stat (file_name, &st) == 0)
149     {
150       XdgMimeCache *cache = _xdg_mime_cache_new_from_file (file_name);
151 
152       if (cache != NULL)
153 	{
154 	  xdg_dir_time_list_add (file_name, st.st_mtime);
155 
156 	  _caches = realloc (_caches, sizeof (XdgMimeCache *) * (n_caches + 2));
157 	  _caches[n_caches] = cache;
158           _caches[n_caches + 1] = NULL;
159 	  n_caches++;
160 
161 	  return FALSE;
162 	}
163     }
164   free (file_name);
165 
166   file_name = malloc (strlen (directory) + strlen ("/mime/globs2") + 1);
167   strcpy (file_name, directory); strcat (file_name, "/mime/globs2");
168   if (stat (file_name, &st) == 0)
169     {
170       _xdg_mime_glob_read_from_file (global_hash, file_name, TRUE);
171       xdg_dir_time_list_add (file_name, st.st_mtime);
172     }
173   else
174     {
175       free (file_name);
176       file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
177       strcpy (file_name, directory); strcat (file_name, "/mime/globs");
178       if (stat (file_name, &st) == 0)
179         {
180           _xdg_mime_glob_read_from_file (global_hash, file_name, FALSE);
181           xdg_dir_time_list_add (file_name, st.st_mtime);
182         }
183       else
184         {
185           free (file_name);
186         }
187     }
188 
189   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
190   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
191   if (stat (file_name, &st) == 0)
192     {
193       _xdg_mime_magic_read_from_file (global_magic, file_name);
194       xdg_dir_time_list_add (file_name, st.st_mtime);
195     }
196   else
197     {
198       free (file_name);
199     }
200 
201   file_name = malloc (strlen (directory) + strlen ("/mime/aliases") + 1);
202   strcpy (file_name, directory); strcat (file_name, "/mime/aliases");
203   _xdg_mime_alias_read_from_file (alias_list, file_name);
204   free (file_name);
205 
206   file_name = malloc (strlen (directory) + strlen ("/mime/subclasses") + 1);
207   strcpy (file_name, directory); strcat (file_name, "/mime/subclasses");
208   _xdg_mime_parent_read_from_file (parent_list, file_name);
209   free (file_name);
210 
211   file_name = malloc (strlen (directory) + strlen ("/mime/icons") + 1);
212   strcpy (file_name, directory); strcat (file_name, "/mime/icons");
213   _xdg_mime_icon_read_from_file (icon_list, file_name);
214   free (file_name);
215 
216   file_name = malloc (strlen (directory) + strlen ("/mime/generic-icons") + 1);
217   strcpy (file_name, directory); strcat (file_name, "/mime/generic-icons");
218   _xdg_mime_icon_read_from_file (generic_icon_list, file_name);
219   free (file_name);
220 
221   return FALSE; /* Keep processing */
222 }
223 
224 /* Runs a command on all the directories in the search path */
225 static void
xdg_run_command_on_dirs(XdgDirectoryFunc func,void * user_data)226 xdg_run_command_on_dirs (XdgDirectoryFunc  func,
227 			 void             *user_data)
228 {
229   const char *xdg_data_home;
230   const char *xdg_data_dirs;
231   const char *ptr;
232 
233   xdg_data_home = getenv ("XDG_DATA_HOME");
234   if (xdg_data_home)
235     {
236       if ((func) (xdg_data_home, user_data))
237 	return;
238     }
239   else
240     {
241       const char *home;
242 
243       home = getenv ("HOME");
244       if (home != NULL)
245 	{
246 	  char *guessed_xdg_home;
247 	  int stop_processing;
248 
249 	  guessed_xdg_home = malloc (strlen (home) + strlen ("/.local/share/") + 1);
250 	  strcpy (guessed_xdg_home, home);
251 	  strcat (guessed_xdg_home, "/.local/share/");
252 	  stop_processing = (func) (guessed_xdg_home, user_data);
253 	  free (guessed_xdg_home);
254 
255 	  if (stop_processing)
256 	    return;
257 	}
258     }
259 
260   xdg_data_dirs = getenv ("XDG_DATA_DIRS");
261   if (xdg_data_dirs == NULL)
262     xdg_data_dirs = "/usr/local/share/:/usr/share/";
263 
264   ptr = xdg_data_dirs;
265 
266   while (*ptr != '\000')
267     {
268       const char *end_ptr;
269       char *dir;
270       int len;
271       int stop_processing;
272 
273       end_ptr = ptr;
274       while (*end_ptr != ':' && *end_ptr != '\000')
275 	end_ptr ++;
276 
277       if (end_ptr == ptr)
278 	{
279 	  ptr++;
280 	  continue;
281 	}
282 
283       if (*end_ptr == ':')
284 	len = end_ptr - ptr;
285       else
286 	len = end_ptr - ptr + 1;
287       dir = malloc (len + 1);
288       strncpy (dir, ptr, len);
289       dir[len] = '\0';
290       stop_processing = (func) (dir, user_data);
291       free (dir);
292 
293       if (stop_processing)
294 	return;
295 
296       ptr = end_ptr;
297     }
298 }
299 
300 /* Checks file_path to make sure it has the same mtime as last time it was
301  * checked.  If it has a different mtime, or if the file doesn't exist, it
302  * returns FALSE.
303  *
304  * FIXME: This doesn't protect against permission changes.
305  */
306 static int
xdg_check_file(const char * file_path,int * exists)307 xdg_check_file (const char *file_path,
308                 int        *exists)
309 {
310   struct stat st;
311 
312   /* If the file exists */
313   if (stat (file_path, &st) == 0)
314     {
315       XdgDirTimeList *list;
316 
317       if (exists)
318         *exists = TRUE;
319 
320       for (list = dir_time_list; list; list = list->next)
321 	{
322 	  if (! strcmp (list->directory_name, file_path))
323 	    {
324 	      if (st.st_mtime == list->mtime)
325 		list->checked = XDG_CHECKED_VALID;
326 	      else
327 		list->checked = XDG_CHECKED_INVALID;
328 
329 	      return (list->checked != XDG_CHECKED_VALID);
330 	    }
331 	}
332       return TRUE;
333     }
334 
335   if (exists)
336     *exists = FALSE;
337 
338   return FALSE;
339 }
340 
341 static int
xdg_check_dir(const char * directory,int * invalid_dir_list)342 xdg_check_dir (const char *directory,
343 	       int        *invalid_dir_list)
344 {
345   int invalid, exists;
346   char *file_name;
347 
348   assert (directory != NULL);
349 
350   /* Check the mime.cache file */
351   file_name = malloc (strlen (directory) + strlen ("/mime/mime.cache") + 1);
352   strcpy (file_name, directory); strcat (file_name, "/mime/mime.cache");
353   invalid = xdg_check_file (file_name, &exists);
354   free (file_name);
355   if (invalid)
356     {
357       *invalid_dir_list = TRUE;
358       return TRUE;
359     }
360   else if (exists)
361     {
362       return FALSE;
363     }
364 
365   /* Check the globs file */
366   file_name = malloc (strlen (directory) + strlen ("/mime/globs") + 1);
367   strcpy (file_name, directory); strcat (file_name, "/mime/globs");
368   invalid = xdg_check_file (file_name, NULL);
369   free (file_name);
370   if (invalid)
371     {
372       *invalid_dir_list = TRUE;
373       return TRUE;
374     }
375 
376   /* Check the magic file */
377   file_name = malloc (strlen (directory) + strlen ("/mime/magic") + 1);
378   strcpy (file_name, directory); strcat (file_name, "/mime/magic");
379   invalid = xdg_check_file (file_name, NULL);
380   free (file_name);
381   if (invalid)
382     {
383       *invalid_dir_list = TRUE;
384       return TRUE;
385     }
386 
387   return FALSE; /* Keep processing */
388 }
389 
390 /* Walks through all the mime files stat()ing them to see if they've changed.
391  * Returns TRUE if they have. */
392 static int
xdg_check_dirs(void)393 xdg_check_dirs (void)
394 {
395   XdgDirTimeList *list;
396   int invalid_dir_list = FALSE;
397 
398   for (list = dir_time_list; list; list = list->next)
399     list->checked = XDG_CHECKED_UNCHECKED;
400 
401   xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_check_dir,
402 			   &invalid_dir_list);
403 
404   if (invalid_dir_list)
405     return TRUE;
406 
407   for (list = dir_time_list; list; list = list->next)
408     {
409       if (list->checked != XDG_CHECKED_VALID)
410 	return TRUE;
411     }
412 
413   return FALSE;
414 }
415 
416 /* We want to avoid stat()ing on every single mime call, so we only look for
417  * newer files every 5 seconds.  This will return TRUE if we need to reread the
418  * mime data from disk.
419  */
420 static int
xdg_check_time_and_dirs(void)421 xdg_check_time_and_dirs (void)
422 {
423   struct timeval tv;
424   time_t current_time;
425   int retval = FALSE;
426 
427   gettimeofday (&tv, NULL);
428   current_time = tv.tv_sec;
429 
430   if (current_time >= last_stat_time + 5)
431     {
432       retval = xdg_check_dirs ();
433       last_stat_time = current_time;
434     }
435 
436   return retval;
437 }
438 
439 /* Called in every public function.  It reloads the hash function if need be.
440  */
441 static void
xdg_mime_init(void)442 xdg_mime_init (void)
443 {
444   if (xdg_check_time_and_dirs ())
445     {
446       xdg_mime_shutdown ();
447     }
448 
449   if (need_reread)
450     {
451       global_hash = _xdg_glob_hash_new ();
452       global_magic = _xdg_mime_magic_new ();
453       alias_list = _xdg_mime_alias_list_new ();
454       parent_list = _xdg_mime_parent_list_new ();
455       icon_list = _xdg_mime_icon_list_new ();
456       generic_icon_list = _xdg_mime_icon_list_new ();
457 
458       xdg_run_command_on_dirs ((XdgDirectoryFunc) xdg_mime_init_from_directory,
459 			       NULL);
460 
461       need_reread = FALSE;
462     }
463 }
464 
465 const char *
xdg_mime_get_mime_type_for_data(const void * data,size_t len,int * result_prio)466 xdg_mime_get_mime_type_for_data (const void *data,
467 				 size_t      len,
468 				 int        *result_prio)
469 {
470   const char *mime_type;
471 
472   if (len == 0)
473     {
474       if (result_prio != NULL)
475         *result_prio = 100;
476       return XDG_MIME_TYPE_EMPTY;
477     }
478 
479   xdg_mime_init ();
480 
481   if (_caches)
482     mime_type = _xdg_mime_cache_get_mime_type_for_data (data, len, result_prio);
483   else
484     mime_type = _xdg_mime_magic_lookup_data (global_magic, data, len, result_prio, NULL, 0);
485 
486   if (mime_type)
487     return mime_type;
488 
489   return _xdg_binary_or_text_fallback(data, len);
490 }
491 
492 const char *
xdg_mime_get_mime_type_for_file(const char * file_name,struct stat * statbuf)493 xdg_mime_get_mime_type_for_file (const char  *file_name,
494                                  struct stat *statbuf)
495 {
496   const char *mime_type;
497   /* currently, only a few globs occur twice, and none
498    * more often, so 5 seems plenty.
499    */
500   const char *mime_types[5];
501   FILE *file;
502   unsigned char *data;
503   int max_extent;
504   int bytes_read;
505   struct stat buf;
506   const char *base_name;
507   int n;
508 
509   if (file_name == NULL)
510     return NULL;
511   if (! _xdg_utf8_validate (file_name))
512     return NULL;
513 
514   xdg_mime_init ();
515 
516   if (_caches)
517     return _xdg_mime_cache_get_mime_type_for_file (file_name, statbuf);
518 
519   base_name = _xdg_get_base_name (file_name);
520   n = _xdg_glob_hash_lookup_file_name (global_hash, base_name, mime_types, 5);
521 
522   if (n == 1)
523     return mime_types[0];
524 
525   if (!statbuf)
526     {
527       if (stat (file_name, &buf) != 0)
528 	return XDG_MIME_TYPE_UNKNOWN;
529 
530       statbuf = &buf;
531     }
532 
533   if (!S_ISREG (statbuf->st_mode))
534     return XDG_MIME_TYPE_UNKNOWN;
535 
536   /* FIXME: Need to make sure that max_extent isn't totally broken.  This could
537    * be large and need getting from a stream instead of just reading it all
538    * in. */
539   max_extent = _xdg_mime_magic_get_buffer_extents (global_magic);
540   data = malloc (max_extent);
541   if (data == NULL)
542     return XDG_MIME_TYPE_UNKNOWN;
543 
544   file = fopen (file_name, "r");
545   if (file == NULL)
546     {
547       free (data);
548       return XDG_MIME_TYPE_UNKNOWN;
549     }
550 
551   bytes_read = fread (data, 1, max_extent, file);
552   if (ferror (file))
553     {
554       free (data);
555       fclose (file);
556       return XDG_MIME_TYPE_UNKNOWN;
557     }
558 
559   mime_type = _xdg_mime_magic_lookup_data (global_magic, data, bytes_read, NULL,
560 					   mime_types, n);
561 
562   if (!mime_type)
563     mime_type = _xdg_binary_or_text_fallback (data, bytes_read);
564 
565   free (data);
566   fclose (file);
567 
568   return mime_type;
569 }
570 
571 const char *
xdg_mime_get_mime_type_from_file_name(const char * file_name)572 xdg_mime_get_mime_type_from_file_name (const char *file_name)
573 {
574   const char *mime_type;
575 
576   xdg_mime_init ();
577 
578   if (_caches)
579     return _xdg_mime_cache_get_mime_type_from_file_name (file_name);
580 
581   if (_xdg_glob_hash_lookup_file_name (global_hash, file_name, &mime_type, 1))
582     return mime_type;
583   else
584     return XDG_MIME_TYPE_UNKNOWN;
585 }
586 
587 int
xdg_mime_get_mime_types_from_file_name(const char * file_name,const char * mime_types[],int n_mime_types)588 xdg_mime_get_mime_types_from_file_name (const char *file_name,
589 					const char  *mime_types[],
590 					int          n_mime_types)
591 {
592   xdg_mime_init ();
593 
594   if (_caches)
595     return _xdg_mime_cache_get_mime_types_from_file_name (file_name, mime_types, n_mime_types);
596 
597   return _xdg_glob_hash_lookup_file_name (global_hash, file_name, mime_types, n_mime_types);
598 }
599 
600 int
xdg_mime_is_valid_mime_type(const char * mime_type)601 xdg_mime_is_valid_mime_type (const char *mime_type)
602 {
603   /* FIXME: We should make this a better test
604    */
605   return _xdg_utf8_validate (mime_type);
606 }
607 
608 void
xdg_mime_shutdown(void)609 xdg_mime_shutdown (void)
610 {
611   XdgCallbackList *list;
612 
613   /* FIXME: Need to make this (and the whole library) thread safe */
614   if (dir_time_list)
615     {
616       xdg_dir_time_list_free (dir_time_list);
617       dir_time_list = NULL;
618     }
619 
620   if (global_hash)
621     {
622       _xdg_glob_hash_free (global_hash);
623       global_hash = NULL;
624     }
625   if (global_magic)
626     {
627       _xdg_mime_magic_free (global_magic);
628       global_magic = NULL;
629     }
630 
631   if (alias_list)
632     {
633       _xdg_mime_alias_list_free (alias_list);
634       alias_list = NULL;
635     }
636 
637   if (parent_list)
638     {
639       _xdg_mime_parent_list_free (parent_list);
640       parent_list = NULL;
641     }
642 
643   if (icon_list)
644     {
645       _xdg_mime_icon_list_free (icon_list);
646       icon_list = NULL;
647     }
648 
649   if (generic_icon_list)
650     {
651       _xdg_mime_icon_list_free (generic_icon_list);
652       generic_icon_list = NULL;
653     }
654 
655   if (_caches)
656     {
657       int i;
658 
659       for (i = 0; i < n_caches; i++)
660         _xdg_mime_cache_unref (_caches[i]);
661       free (_caches);
662       _caches = NULL;
663       n_caches = 0;
664     }
665 
666   for (list = callback_list; list; list = list->next)
667     (list->callback) (list->data);
668 
669   need_reread = TRUE;
670 }
671 
672 int
xdg_mime_get_max_buffer_extents(void)673 xdg_mime_get_max_buffer_extents (void)
674 {
675   xdg_mime_init ();
676 
677   if (_caches)
678     return _xdg_mime_cache_get_max_buffer_extents ();
679 
680   return _xdg_mime_magic_get_buffer_extents (global_magic);
681 }
682 
683 const char *
_xdg_mime_unalias_mime_type(const char * mime_type)684 _xdg_mime_unalias_mime_type (const char *mime_type)
685 {
686   const char *lookup;
687 
688   if (_caches)
689     return _xdg_mime_cache_unalias_mime_type (mime_type);
690 
691   if ((lookup = _xdg_mime_alias_list_lookup (alias_list, mime_type)) != NULL)
692     return lookup;
693 
694   return mime_type;
695 }
696 
697 const char *
xdg_mime_unalias_mime_type(const char * mime_type)698 xdg_mime_unalias_mime_type (const char *mime_type)
699 {
700   xdg_mime_init ();
701 
702   return _xdg_mime_unalias_mime_type (mime_type);
703 }
704 
705 int
_xdg_mime_mime_type_equal(const char * mime_a,const char * mime_b)706 _xdg_mime_mime_type_equal (const char *mime_a,
707 			   const char *mime_b)
708 {
709   const char *unalias_a, *unalias_b;
710 
711   unalias_a = _xdg_mime_unalias_mime_type (mime_a);
712   unalias_b = _xdg_mime_unalias_mime_type (mime_b);
713 
714   if (strcmp (unalias_a, unalias_b) == 0)
715     return 1;
716 
717   return 0;
718 }
719 
720 int
xdg_mime_mime_type_equal(const char * mime_a,const char * mime_b)721 xdg_mime_mime_type_equal (const char *mime_a,
722 			  const char *mime_b)
723 {
724   xdg_mime_init ();
725 
726   return _xdg_mime_mime_type_equal (mime_a, mime_b);
727 }
728 
729 int
xdg_mime_media_type_equal(const char * mime_a,const char * mime_b)730 xdg_mime_media_type_equal (const char *mime_a,
731 			   const char *mime_b)
732 {
733   char *sep;
734 
735   sep = strchr (mime_a, '/');
736 
737   if (sep && strncmp (mime_a, mime_b, sep - mime_a + 1) == 0)
738     return 1;
739 
740   return 0;
741 }
742 
743 #if 1
744 static int
ends_with(const char * str,const char * suffix)745 ends_with (const char *str,
746            const char *suffix)
747 {
748   int length;
749   int suffix_length;
750 
751   length = strlen (str);
752   suffix_length = strlen (suffix);
753   if (length < suffix_length)
754     return 0;
755 
756   if (strcmp (str + length - suffix_length, suffix) == 0)
757     return 1;
758 
759   return 0;
760 }
761 
762 static int
xdg_mime_is_super_type(const char * mime)763 xdg_mime_is_super_type (const char *mime)
764 {
765   return ends_with (mime, "/*");
766 }
767 #endif
768 
769 int
_xdg_mime_mime_type_subclass(const char * mime,const char * base)770 _xdg_mime_mime_type_subclass (const char *mime,
771 			      const char *base)
772 {
773   const char *umime, *ubase;
774   const char **parents;
775 
776   if (_caches)
777     return _xdg_mime_cache_mime_type_subclass (mime, base);
778 
779   umime = _xdg_mime_unalias_mime_type (mime);
780   ubase = _xdg_mime_unalias_mime_type (base);
781 
782   if (strcmp (umime, ubase) == 0)
783     return 1;
784 
785 #if 1
786   /* Handle supertypes */
787   if (xdg_mime_is_super_type (ubase) &&
788       xdg_mime_media_type_equal (umime, ubase))
789     return 1;
790 #endif
791 
792   /*  Handle special cases text/plain and application/octet-stream */
793   if (strcmp (ubase, "text/plain") == 0 &&
794       strncmp (umime, "text/", 5) == 0)
795     return 1;
796 
797   if (strcmp (ubase, "application/octet-stream") == 0 &&
798       strncmp (umime, "inode/", 6) != 0)
799     return 1;
800 
801   parents = _xdg_mime_parent_list_lookup (parent_list, umime);
802   for (; parents && *parents; parents++)
803     {
804       if (_xdg_mime_mime_type_subclass (*parents, ubase))
805 	return 1;
806     }
807 
808   return 0;
809 }
810 
811 int
xdg_mime_mime_type_subclass(const char * mime,const char * base)812 xdg_mime_mime_type_subclass (const char *mime,
813 			     const char *base)
814 {
815   xdg_mime_init ();
816 
817   return _xdg_mime_mime_type_subclass (mime, base);
818 }
819 
820 char **
xdg_mime_list_mime_parents(const char * mime)821 xdg_mime_list_mime_parents (const char *mime)
822 {
823   const char **parents;
824   char **result;
825   int i, n;
826 
827   if (_caches)
828     return _xdg_mime_cache_list_mime_parents (mime);
829 
830   parents = xdg_mime_get_mime_parents (mime);
831 
832   if (!parents)
833     return NULL;
834 
835   for (i = 0; parents[i]; i++) ;
836 
837   n = (i + 1) * sizeof (char *);
838   result = (char **) malloc (n);
839   memcpy (result, parents, n);
840 
841   return result;
842 }
843 
844 const char **
xdg_mime_get_mime_parents(const char * mime)845 xdg_mime_get_mime_parents (const char *mime)
846 {
847   const char *umime;
848 
849   xdg_mime_init ();
850 
851   umime = _xdg_mime_unalias_mime_type (mime);
852 
853   return _xdg_mime_parent_list_lookup (parent_list, umime);
854 }
855 
856 void
xdg_mime_dump(void)857 xdg_mime_dump (void)
858 {
859   xdg_mime_init();
860 
861   printf ("*** ALIASES ***\n\n");
862   _xdg_mime_alias_list_dump (alias_list);
863   printf ("\n*** PARENTS ***\n\n");
864   _xdg_mime_parent_list_dump (parent_list);
865   printf ("\n*** CACHE ***\n\n");
866   _xdg_glob_hash_dump (global_hash);
867   printf ("\n*** GLOBS ***\n\n");
868   _xdg_glob_hash_dump (global_hash);
869   printf ("\n*** GLOBS REVERSE TREE ***\n\n");
870   _xdg_mime_cache_glob_dump ();
871 }
872 
873 
874 /* Registers a function to be called every time the mime database reloads its files
875  */
876 int
xdg_mime_register_reload_callback(XdgMimeCallback callback,void * data,XdgMimeDestroy destroy)877 xdg_mime_register_reload_callback (XdgMimeCallback  callback,
878 				   void            *data,
879 				   XdgMimeDestroy   destroy)
880 {
881   XdgCallbackList *list_el;
882   static int callback_id = 1;
883 
884   /* Make a new list element */
885   list_el = calloc (1, sizeof (XdgCallbackList));
886   list_el->callback_id = callback_id;
887   list_el->callback = callback;
888   list_el->data = data;
889   list_el->destroy = destroy;
890   list_el->next = callback_list;
891   if (list_el->next)
892     list_el->next->prev = list_el;
893 
894   callback_list = list_el;
895   callback_id ++;
896 
897   return callback_id - 1;
898 }
899 
900 void
xdg_mime_remove_callback(int callback_id)901 xdg_mime_remove_callback (int callback_id)
902 {
903   XdgCallbackList *list;
904 
905   for (list = callback_list; list; list = list->next)
906     {
907       if (list->callback_id == callback_id)
908 	{
909 	  if (list->next)
910 	    list->next = list->prev;
911 
912 	  if (list->prev)
913 	    list->prev->next = list->next;
914 	  else
915 	    callback_list = list->next;
916 
917 	  /* invoke the destroy handler */
918 	  (list->destroy) (list->data);
919 	  free (list);
920 	  return;
921 	}
922     }
923 }
924 
925 const char *
xdg_mime_get_icon(const char * mime)926 xdg_mime_get_icon (const char *mime)
927 {
928   xdg_mime_init ();
929 
930   if (_caches)
931     return _xdg_mime_cache_get_icon (mime);
932 
933   return _xdg_mime_icon_list_lookup (icon_list, mime);
934 }
935 
936 const char *
xdg_mime_get_generic_icon(const char * mime)937 xdg_mime_get_generic_icon (const char *mime)
938 {
939   xdg_mime_init ();
940 
941   if (_caches)
942     return _xdg_mime_cache_get_generic_icon (mime);
943 
944   return _xdg_mime_icon_list_lookup (generic_icon_list, mime);
945 }
946