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