(root)/
glib-2.79.0/
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   * SPDX-License-Identifier: LGPL-2.1-or-later or AFL-2.0
       9   */
      10  
      11  #ifdef HAVE_CONFIG_H
      12  #include <config.h>
      13  #endif
      14  
      15  #include <stdio.h>
      16  #include <stdlib.h>
      17  #include <string.h>
      18  
      19  #include <fcntl.h>
      20  #include <unistd.h>
      21  #include <errno.h>
      22  #include <fnmatch.h>
      23  #include <assert.h>
      24  
      25  #include <netinet/in.h> /* for ntohl/ntohs */
      26  
      27  #ifdef HAVE_MMAP
      28  #include <sys/mman.h>
      29  #else
      30  #warning Building xdgmime without MMAP support. Binary "mime.cache" files will not be used.
      31  #endif
      32  
      33  #include <sys/stat.h>
      34  #include <sys/types.h>
      35  
      36  #include "xdgmimecache.h"
      37  #include "xdgmimeint.h"
      38  
      39  #ifndef MAX
      40  #define MAX(a,b) ((a) > (b) ? (a) : (b))
      41  #endif
      42  
      43  #ifndef	FALSE
      44  #define	FALSE	(0)
      45  #endif
      46  
      47  #ifndef	TRUE
      48  #define	TRUE	(!FALSE)
      49  #endif
      50  
      51  #ifndef _O_BINARY
      52  #define _O_BINARY 0
      53  #endif
      54  
      55  #ifndef MAP_FAILED
      56  #define MAP_FAILED ((void *) -1)
      57  #endif
      58  
      59  #ifndef S_ISREG
      60  #define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
      61  #endif
      62  
      63  #define MAJOR_VERSION 1
      64  #define MINOR_VERSION_MIN 1
      65  #define MINOR_VERSION_MAX 2
      66  
      67  struct _XdgMimeCache
      68  {
      69    int ref_count;
      70    int minor;
      71  
      72    size_t  size;
      73    char   *buffer;
      74  };
      75  
      76  #define GET_UINT16(cache,offset) (ntohs(*(xdg_uint16_t*)((cache) + (offset))))
      77  #define GET_UINT32(cache,offset) (ntohl(*(xdg_uint32_t*)((cache) + (offset))))
      78  
      79  // Validates that it is safe to call GET_UINT32() at
      80  // cache->buffer[offset + (n * record_size)]. Ensures that offset is aligned to
      81  // a 4-byte boundary, and that offset+(n*record_size) does not overflow.
      82  // `record_size` values are known constants and never 0.
      83  #define OUT_OF_BOUNDS(offset,n,record_size,max) \
      84    (((offset) & 0x3) || (offset) > (max) || (n) > ((max) - (offset)) / (record_size))
      85  
      86  XdgMimeCache *
      87  _xdg_mime_cache_ref (XdgMimeCache *cache)
      88  {
      89    cache->ref_count++;
      90    return cache;
      91  }
      92  
      93  void
      94  _xdg_mime_cache_unref (XdgMimeCache *cache)
      95  {
      96    cache->ref_count--;
      97  
      98    if (cache->ref_count == 0)
      99      {
     100  #ifdef HAVE_MMAP
     101        munmap (cache->buffer, cache->size);
     102  #endif
     103        free (cache);
     104      }
     105  }
     106  
     107  XdgMimeCache *
     108  _xdg_mime_cache_new_from_file (const char *file_name)
     109  {
     110    XdgMimeCache *cache = NULL;
     111  
     112  #ifdef HAVE_MMAP
     113    int fd = -1;
     114    struct stat st;
     115    char *buffer = NULL;
     116    int minor;
     117  
     118    /* Open the file and map it into memory */
     119    do {
     120      fd = open (file_name, O_RDONLY|_O_BINARY, 0);
     121    } while (fd == -1 && errno == EINTR);
     122  
     123    if (fd < 0)
     124      return NULL;
     125    
     126    // A valid cache must be at least 40 bytes for the header.
     127    if (fstat (fd, &st) < 0 || st.st_size < 40)
     128      goto done;
     129  
     130    buffer = (char *) mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
     131  
     132    if (buffer == MAP_FAILED)
     133      goto done;
     134  
     135    minor = GET_UINT16 (buffer, 2);
     136    /* Verify version */
     137    if (GET_UINT16 (buffer, 0) != MAJOR_VERSION ||
     138        (minor < MINOR_VERSION_MIN ||
     139         minor > MINOR_VERSION_MAX))
     140      {
     141        munmap (buffer, st.st_size);
     142  
     143        goto done;
     144      }
     145    
     146    cache = (XdgMimeCache *) malloc (sizeof (XdgMimeCache));
     147    cache->minor = minor;
     148    cache->ref_count = 1;
     149    cache->buffer = buffer;
     150    cache->size = st.st_size;
     151  
     152   done:
     153    if (fd != -1)
     154      close (fd);
     155  
     156  #else /* HAVE_MMAP */
     157    cache = (XdgMimeCache *) malloc (sizeof (XdgMimeCache));
     158    cache->minor = 0;
     159    cache->ref_count = 1;
     160    cache->buffer = NULL;
     161    cache->size = 0;
     162  #endif  /* HAVE_MMAP */
     163  
     164    return cache;
     165  }
     166  
     167  static int
     168  cache_magic_matchlet_compare_to_data (XdgMimeCache *cache, 
     169  				      xdg_uint32_t  offset,
     170  				      const void   *data,
     171  				      size_t        len)
     172  {
     173    xdg_uint32_t range_start = GET_UINT32 (cache->buffer, offset);
     174    xdg_uint32_t range_length = GET_UINT32 (cache->buffer, offset + 4);
     175    xdg_uint32_t data_length = GET_UINT32 (cache->buffer, offset + 12);
     176    xdg_uint32_t data_offset = GET_UINT32 (cache->buffer, offset + 16);
     177    xdg_uint32_t mask_offset = GET_UINT32 (cache->buffer, offset + 20);
     178    
     179    xdg_uint32_t i, j;
     180  
     181    for (i = range_start; i < range_start + range_length; i++)
     182      {
     183        int valid_matchlet = TRUE;
     184        
     185        if (i + data_length > len)
     186  	return FALSE;
     187  
     188        if (mask_offset)
     189  	{
     190  	  for (j = 0; j < data_length; j++)
     191  	    {
     192  	      if ((((unsigned char *)cache->buffer)[data_offset + j] & ((unsigned char *)cache->buffer)[mask_offset + j]) !=
     193  		  ((((unsigned char *) data)[j + i]) & ((unsigned char *)cache->buffer)[mask_offset + j]))
     194  		{
     195  		  valid_matchlet = FALSE;
     196  		  break;
     197  		}
     198  	    }
     199  	}
     200        else
     201  	{
     202  	  valid_matchlet = memcmp(cache->buffer + data_offset, (unsigned char *)data + i, data_length) == 0;
     203  	}
     204  
     205        if (valid_matchlet)
     206  	return TRUE;
     207      }
     208    
     209    return FALSE;  
     210  }
     211  
     212  static int
     213  cache_magic_matchlet_compare (XdgMimeCache *cache, 
     214  			      xdg_uint32_t  offset,
     215  			      const void   *data,
     216  			      size_t        len)
     217  {
     218    xdg_uint32_t n_children = GET_UINT32 (cache->buffer, offset + 24);
     219    xdg_uint32_t child_offset = GET_UINT32 (cache->buffer, offset + 28);
     220    if (OUT_OF_BOUNDS (child_offset, n_children, 32, cache->size))
     221      return FALSE;
     222  
     223    xdg_uint32_t 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    if (OUT_OF_BOUNDS (matchlet_offset, n_matchlets, 32, cache->size))
     253      return NULL;
     254  
     255    xdg_uint32_t i;
     256  
     257    for (i = 0; i < n_matchlets; i++)
     258      {
     259        if (cache_magic_matchlet_compare (cache, matchlet_offset + i * 32, 
     260  					data, len))
     261  	{
     262  	  *prio = priority;
     263  	  
     264  	  return cache->buffer + mimetype_offset;
     265  	}
     266      }
     267  
     268    return NULL;
     269  }
     270  
     271  static const char *
     272  cache_magic_lookup_data (XdgMimeCache *cache, 
     273  			 const void   *data, 
     274  			 size_t        len, 
     275  			 int          *prio)
     276  {
     277    xdg_uint32_t list_offset;
     278    xdg_uint32_t n_entries;
     279    xdg_uint32_t offset;
     280  
     281    xdg_uint32_t j;
     282  
     283    *prio = 0;
     284  
     285    list_offset = GET_UINT32 (cache->buffer, 24);
     286    if (OUT_OF_BOUNDS (list_offset, 1, 12, cache->size))
     287      return NULL;
     288  
     289    n_entries = GET_UINT32 (cache->buffer, list_offset);
     290    offset = GET_UINT32 (cache->buffer, list_offset + 8);
     291    if (OUT_OF_BOUNDS (offset, n_entries, 16, cache->size))
     292      return NULL;
     293    
     294    for (j = 0; j < n_entries; j++)
     295      {
     296        const char *match;
     297  
     298        match = cache_magic_compare_to_data (cache, offset + 16 * j, 
     299  					   data, len, prio);
     300        if (match)
     301  	return match;
     302      }
     303  
     304    return NULL;
     305  }
     306  
     307  static const char *
     308  cache_alias_lookup (const char *alias)
     309  {
     310    const char *ptr;
     311    int i, min, max, mid, cmp;
     312  
     313    for (i = 0; _caches[i]; i++)
     314      {
     315        XdgMimeCache *cache = _caches[i];
     316        xdg_uint32_t list_offset;
     317        xdg_uint32_t n_entries;
     318        xdg_uint32_t offset;
     319  
     320        if (cache->buffer == NULL)
     321          continue;
     322  
     323        list_offset = GET_UINT32 (cache->buffer, 4);
     324        if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
     325          continue;
     326  
     327        n_entries = GET_UINT32 (cache->buffer, list_offset);
     328        if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
     329          continue;
     330  
     331        min = 0; 
     332        max = n_entries - 1;
     333        while (max >= min) 
     334  	{
     335  	  mid = (min + max) / 2;
     336  
     337  	  offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid);
     338  	  ptr = cache->buffer + offset;
     339  	  cmp = strcmp (ptr, alias);
     340  	  
     341  	  if (cmp < 0)
     342  	    min = mid + 1;
     343  	  else if (cmp > 0)
     344  	    max = mid - 1;
     345  	  else
     346  	    {
     347  	      offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4);
     348  	      return cache->buffer + offset;
     349  	    }
     350  	}
     351      }
     352  
     353    return NULL;
     354  }
     355  
     356  typedef struct {
     357    const char *mime;
     358    int weight;
     359  } MimeWeight;
     360  
     361  static int
     362  cache_glob_lookup_literal (const char *file_name,
     363  			   const char *mime_types[],
     364  			   int         n_mime_types,
     365  			   int         case_sensitive_check)
     366  {
     367    const char *ptr;
     368    int i, min, max, mid, cmp;
     369  
     370    assert (n_mime_types > 0);
     371  
     372    for (i = 0; _caches[i]; i++)
     373      {
     374        XdgMimeCache *cache = _caches[i];
     375        xdg_uint32_t list_offset;
     376        xdg_uint32_t n_entries;
     377        xdg_uint32_t offset;
     378  
     379        if (cache->buffer == NULL)
     380          continue;
     381  
     382        list_offset = GET_UINT32 (cache->buffer, 12);
     383        if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
     384          continue;
     385  
     386        n_entries = GET_UINT32 (cache->buffer, list_offset);
     387        if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 12, cache->size))
     388          continue;
     389  
     390        min = 0; 
     391        max = n_entries - 1;
     392        while (max >= min) 
     393  	{
     394  	  mid = (min + max) / 2;
     395  
     396  	  offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid);
     397  	  ptr = cache->buffer + offset;
     398  	  cmp = strcmp (ptr, file_name);
     399  
     400  	  if (cmp < 0)
     401  	    min = mid + 1;
     402  	  else if (cmp > 0)
     403  	    max = mid - 1;
     404  	  else
     405  	    {
     406  	      int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 8);
     407  	      int case_sensitive = weight & 0x100;
     408  	      weight = weight & 0xff;
     409  
     410  	      if (case_sensitive_check || !case_sensitive)
     411  		{
     412  		  offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * mid + 4);
     413  		  mime_types[0] = (const char *)(cache->buffer + offset);
     414  
     415  		  return 1;
     416  		}
     417  	      return 0;
     418  	    }
     419  	}
     420      }
     421  
     422    return 0;
     423  }
     424  
     425  static int
     426  cache_glob_lookup_fnmatch (const char *file_name,
     427  			   MimeWeight  mime_types[],
     428  			   int         n_mime_types,
     429  			   int         case_sensitive_check)
     430  {
     431    const char *mime_type;
     432    const char *ptr;
     433  
     434    int i, n;
     435    xdg_uint32_t j;
     436  
     437    n = 0;
     438    for (i = 0; _caches[i]; i++)
     439      {
     440        XdgMimeCache *cache = _caches[i];
     441  
     442        xdg_uint32_t list_offset;
     443        xdg_uint32_t n_entries;
     444  
     445        if (cache->buffer == NULL)
     446          continue;
     447  
     448        list_offset = GET_UINT32 (cache->buffer, 20);
     449        if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
     450          continue;
     451  
     452        n_entries = GET_UINT32 (cache->buffer, list_offset);
     453        if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 12, cache->size))
     454          continue;
     455  
     456        for (j = 0; j < n_entries && n < n_mime_types; j++)
     457  	{
     458  	  xdg_uint32_t offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j);
     459  	  xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 4);
     460  	  int weight = GET_UINT32 (cache->buffer, list_offset + 4 + 12 * j + 8);
     461  	  int case_sensitive = weight & 0x100;
     462  	  weight = weight & 0xff;
     463  	  ptr = cache->buffer + offset;
     464  	  mime_type = cache->buffer + mimetype_offset;
     465  	  if (case_sensitive_check || !case_sensitive)
     466  	    {
     467  	      /* FIXME: Not UTF-8 safe */
     468  	      if (fnmatch (ptr, file_name, 0) == 0)
     469  	        {
     470  	          mime_types[n].mime = mime_type;
     471  	          mime_types[n].weight = weight;
     472  	          n++;
     473  	        }
     474  	    }
     475  	}
     476  
     477        if (n == n_mime_types)
     478  	break;
     479      }
     480  
     481    return n;
     482  }
     483  
     484  static int
     485  cache_glob_node_lookup_suffix (XdgMimeCache  *cache,
     486  			       xdg_uint32_t   n_entries,
     487  			       xdg_uint32_t   offset,
     488  			       const char    *file_name,
     489  			       int            len,
     490  			       int            case_sensitive_check,
     491  			       MimeWeight     mime_types[],
     492  			       int            n_mime_types)
     493  {
     494    xdg_unichar_t character;
     495    xdg_unichar_t match_char;
     496    xdg_uint32_t mimetype_offset;
     497    xdg_uint32_t n_children;
     498    xdg_uint32_t child_offset; 
     499    int weight;
     500    int case_sensitive;
     501  
     502    xdg_uint32_t i;
     503    int min, max, mid, n;
     504  
     505    character = file_name[len - 1];
     506  
     507    assert (character != 0);
     508  
     509    min = 0;
     510    max = n_entries - 1;
     511    while (max >= min)
     512      {
     513        mid = (min + max) /  2;
     514        match_char = GET_UINT32 (cache->buffer, offset + 12 * mid);
     515        if (match_char < character)
     516  	min = mid + 1;
     517        else if (match_char > character)
     518  	max = mid - 1;
     519        else 
     520  	{
     521            len--;
     522            n = 0;
     523            n_children = GET_UINT32 (cache->buffer, offset + 12 * mid + 4);
     524            child_offset = GET_UINT32 (cache->buffer, offset + 12 * mid + 8);
     525            if (OUT_OF_BOUNDS (child_offset, n_children, 12, cache->size))
     526              continue;
     527        
     528            if (len > 0)
     529              {
     530                n = cache_glob_node_lookup_suffix (cache, 
     531                                                   n_children, child_offset,
     532                                                   file_name, len, 
     533                                                   case_sensitive_check,
     534                                                   mime_types,
     535                                                   n_mime_types);
     536              }
     537            if (n == 0)
     538              {
     539  	      i = 0;
     540  	      while (n < n_mime_types && i < n_children)
     541  		{
     542  		  match_char = GET_UINT32 (cache->buffer, child_offset + 12 * i);
     543  		  if (match_char != 0)
     544  		    break;
     545  
     546  		  mimetype_offset = GET_UINT32 (cache->buffer, child_offset + 12 * i + 4);
     547  		  weight = GET_UINT32 (cache->buffer, child_offset + 12 * i + 8);
     548  		  case_sensitive = weight & 0x100;
     549  		  weight = weight & 0xff;
     550  
     551  		  if (case_sensitive_check || !case_sensitive)
     552  		    {
     553  		      mime_types[n].mime = cache->buffer + mimetype_offset;
     554  		      mime_types[n].weight = weight;
     555  		      n++;
     556  		    }
     557  		  i++;
     558  		}
     559  	    }
     560  	  return n;
     561  	}
     562      }
     563    return 0;
     564  }
     565  
     566  static int
     567  cache_glob_lookup_suffix (const char *file_name,
     568  			  int         len,
     569  			  int         ignore_case,
     570  			  MimeWeight  mime_types[],
     571  			  int         n_mime_types)
     572  {
     573    int i, n;
     574  
     575    n = 0;
     576    for (i = 0; _caches[i]; i++)
     577      {
     578        XdgMimeCache *cache = _caches[i];
     579  
     580        xdg_uint32_t list_offset;
     581        xdg_uint32_t n_entries;
     582        xdg_uint32_t offset;
     583  
     584        if (cache->buffer == NULL)
     585          continue;
     586  
     587        list_offset = GET_UINT32 (cache->buffer, 16);
     588        if (OUT_OF_BOUNDS (list_offset, 1, 8, cache->size))
     589          continue;
     590  
     591        n_entries = GET_UINT32 (cache->buffer, list_offset);
     592        offset = GET_UINT32 (cache->buffer, list_offset + 4);
     593        if (OUT_OF_BOUNDS (offset, n_entries, 12, cache->size))
     594          continue;
     595  
     596        n += cache_glob_node_lookup_suffix (cache,
     597  					  n_entries, offset,
     598  					  file_name, len,
     599  					  ignore_case,
     600  					  mime_types + n,
     601  					  n_mime_types - n);
     602        if (n == n_mime_types)
     603  	break;
     604      }
     605  
     606    return n;
     607  }
     608  
     609  static int compare_mime_weight (const void *a, const void *b)
     610  {
     611    const MimeWeight *aa = (const MimeWeight *)a;
     612    const MimeWeight *bb = (const MimeWeight *)b;
     613  
     614    return bb->weight - aa->weight;
     615  }
     616  
     617  #define ISUPPER(c)		((c) >= 'A' && (c) <= 'Z')
     618  static char *
     619  ascii_tolower (const char *str)
     620  {
     621    char *p, *lower;
     622  
     623    lower = strdup (str);
     624    p = lower;
     625    while (*p != 0)
     626      {
     627        char c = *p;
     628        *p++ = ISUPPER (c) ? c - 'A' + 'a' : c;
     629      }
     630    return lower;
     631  }
     632  
     633  static int
     634  filter_out_dupes (MimeWeight mimes[], int n_mimes)
     635  {
     636    int last;
     637    int i, j;
     638  
     639    last = n_mimes;
     640  
     641    for (i = 0; i < last; i++)
     642      {
     643        j = i + 1;
     644        while (j < last)
     645          {
     646            if (strcmp (mimes[i].mime, mimes[j].mime) == 0)
     647              {
     648                mimes[i].weight = MAX (mimes[i].weight, mimes[j].weight);
     649                last--;
     650                mimes[j].mime = mimes[last].mime;
     651                mimes[j].weight = mimes[last].weight;
     652              }
     653            else
     654              j++;
     655          }
     656      }
     657  
     658    return last;
     659  }
     660  
     661  static int
     662  cache_glob_lookup_file_name (const char *file_name,
     663  			     const char *mime_types[],
     664  			     int         n_mime_types)
     665  {
     666    int n;
     667    MimeWeight mimes[10];
     668    int n_mimes = 10;
     669    int i;
     670    int len;
     671    char *lower_case;
     672  
     673    assert (file_name != NULL && n_mime_types > 0);
     674  
     675    /* First, check the literals */
     676  
     677    lower_case = ascii_tolower (file_name);
     678  
     679    n = cache_glob_lookup_literal (lower_case, mime_types, n_mime_types, FALSE);
     680    if (n > 0)
     681      {
     682        free (lower_case);
     683        return n;
     684      }
     685  
     686    n = cache_glob_lookup_literal (file_name, mime_types, n_mime_types, TRUE);
     687    if (n > 0)
     688      {
     689        free (lower_case);
     690        return n;
     691      }
     692  
     693    len = strlen (file_name);
     694    n = cache_glob_lookup_suffix (lower_case, len, FALSE, mimes, n_mimes);
     695    if (n < 2)
     696      n += cache_glob_lookup_suffix (file_name, len, TRUE, mimes + n, n_mimes - n);
     697  
     698    /* Last, try fnmatch */
     699    if (n == 0)
     700      n = cache_glob_lookup_fnmatch (lower_case, mimes, n_mimes, FALSE);
     701    if (n < 2)
     702      n += cache_glob_lookup_fnmatch (file_name, mimes + n, n_mimes - n, TRUE);
     703  
     704    n = filter_out_dupes (mimes, n);
     705  
     706    free (lower_case);
     707  
     708    qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight);
     709  
     710    if (n_mime_types < n)
     711      n = n_mime_types;
     712  
     713    for (i = 0; i < n; i++)
     714      mime_types[i] = mimes[i].mime;
     715  
     716    return n;
     717  }
     718  
     719  int
     720  _xdg_mime_cache_get_max_buffer_extents (void)
     721  {
     722    xdg_uint32_t offset;
     723    xdg_uint32_t max_extent;
     724    int i;
     725  
     726    max_extent = 0;
     727    for (i = 0; _caches[i]; i++)
     728      {
     729        XdgMimeCache *cache = _caches[i];
     730  
     731        if (cache->buffer == NULL)
     732          continue;
     733  
     734        offset = GET_UINT32 (cache->buffer, 24);
     735        if (OUT_OF_BOUNDS (offset, 1, 8, cache->size))
     736          continue;
     737  
     738        max_extent = MAX (max_extent, GET_UINT32 (cache->buffer, offset + 4));
     739      }
     740  
     741    return max_extent;
     742  }
     743  
     744  static const char *
     745  cache_get_mime_type_for_data (const void *data,
     746  			      size_t      len,
     747  			      int        *result_prio,
     748  			      const char *mime_types[],
     749  			      int         n_mime_types)
     750  {
     751    const char *mime_type;
     752    int i, n, priority;
     753  
     754    priority = 0;
     755    mime_type = NULL;
     756    for (i = 0; _caches[i]; i++)
     757      {
     758        XdgMimeCache *cache = _caches[i];
     759  
     760        int prio;
     761        const char *match;
     762  
     763        if (cache->buffer == NULL)
     764          continue;
     765  
     766        match = cache_magic_lookup_data (cache, data, len, &prio);
     767        if (prio > priority)
     768  	{
     769  	  priority = prio;
     770  	  mime_type = match;
     771  	}
     772      }
     773  
     774    if (result_prio)
     775      *result_prio = priority;
     776  
     777    if (priority > 0)
     778      {
     779        /* Pick glob-result R where mime_type inherits from R */
     780        for (n = 0; n < n_mime_types; n++)
     781          {
     782            if (mime_types[n] && _xdg_mime_cache_mime_type_subclass (mime_types[n], mime_type, NULL))
     783              return mime_types[n];
     784          }
     785        if (n == 0)
     786          {
     787            /* No globs: return magic match */
     788            return mime_type;
     789          }
     790      }
     791  
     792    /* Pick first glob result, as fallback */
     793    for (n = 0; n < n_mime_types; n++)
     794      {
     795        if (mime_types[n])
     796          return mime_types[n];
     797      }
     798  
     799    return NULL;
     800  }
     801  
     802  const char *
     803  _xdg_mime_cache_get_mime_type_for_data (const void *data,
     804  					size_t      len,
     805  					int        *result_prio)
     806  {
     807    return cache_get_mime_type_for_data (data, len, result_prio, NULL, 0);
     808  }
     809  
     810  const char *
     811  _xdg_mime_cache_get_mime_type_for_file (const char  *file_name,
     812  					struct stat *statbuf)
     813  {
     814    const char *mime_type;
     815    const char *mime_types[10];
     816    FILE *file;
     817    unsigned char *data;
     818    int max_extent;
     819    int bytes_read;
     820    struct stat buf;
     821    const char *base_name;
     822    int n;
     823  
     824    if (file_name == NULL)
     825      return NULL;
     826  
     827    if (! _xdg_utf8_validate (file_name))
     828      return NULL;
     829  
     830    base_name = _xdg_get_base_name (file_name);
     831    n = cache_glob_lookup_file_name (base_name, mime_types, 10);
     832  
     833    if (n == 1)
     834      return mime_types[0];
     835  
     836    if (!statbuf)
     837      {
     838        if (stat (file_name, &buf) != 0)
     839  	return XDG_MIME_TYPE_UNKNOWN;
     840  
     841        statbuf = &buf;
     842      }
     843  
     844    if (statbuf->st_size == 0)
     845      return XDG_MIME_TYPE_EMPTY;
     846  
     847    if (!S_ISREG (statbuf->st_mode))
     848      return XDG_MIME_TYPE_UNKNOWN;
     849  
     850    /* FIXME: Need to make sure that max_extent isn't totally broken.  This could
     851     * be large and need getting from a stream instead of just reading it all
     852     * in. */
     853    max_extent = _xdg_mime_cache_get_max_buffer_extents ();
     854    data = malloc (max_extent);
     855    if (data == NULL)
     856      return XDG_MIME_TYPE_UNKNOWN;
     857          
     858    file = fopen (file_name, "r");
     859    if (file == NULL)
     860      {
     861        free (data);
     862        return XDG_MIME_TYPE_UNKNOWN;
     863      }
     864  
     865    bytes_read = fread (data, 1, max_extent, file);
     866    if (ferror (file))
     867      {
     868        free (data);
     869        fclose (file);
     870        return XDG_MIME_TYPE_UNKNOWN;
     871      }
     872  
     873    mime_type = cache_get_mime_type_for_data (data, bytes_read, NULL,
     874  					    mime_types, n);
     875  
     876    if (!mime_type)
     877      mime_type = _xdg_binary_or_text_fallback (data, bytes_read);
     878  
     879    free (data);
     880    fclose (file);
     881  
     882    return mime_type;
     883  }
     884  
     885  const char *
     886  _xdg_mime_cache_get_mime_type_from_file_name (const char *file_name)
     887  {
     888    const char *mime_type;
     889  
     890    if (cache_glob_lookup_file_name (file_name, &mime_type, 1))
     891      return mime_type;
     892    else
     893      return XDG_MIME_TYPE_UNKNOWN;
     894  }
     895  
     896  int
     897  _xdg_mime_cache_get_mime_types_from_file_name (const char *file_name,
     898  					       const char  *mime_types[],
     899  					       int          n_mime_types)
     900  {
     901    return cache_glob_lookup_file_name (file_name, mime_types, n_mime_types);
     902  }
     903  
     904  #if 1
     905  static int
     906  ends_with (const char *str,
     907             const char *suffix)
     908  {
     909    int length;
     910    int suffix_length;
     911  
     912    length = strlen (str);
     913    suffix_length = strlen (suffix);
     914    if (length < suffix_length)
     915      return 0;
     916  
     917    if (strcmp (str + length - suffix_length, suffix) == 0)
     918      return 1;
     919  
     920    return 0;
     921  }
     922  
     923  static int
     924  is_super_type (const char *mime)
     925  {
     926    return ends_with (mime, "/*");
     927  }
     928  #endif
     929  
     930  int
     931  _xdg_mime_cache_mime_type_subclass (const char *mime,
     932  				    const char *base,
     933  				    const char ***seen)
     934  {
     935    const char *umime, *ubase, *parent;
     936    const char **first_seen = NULL, **new_seen;
     937  
     938    xdg_uint32_t j;
     939    int i, k, min, max, med, cmp, ret = 0;
     940  
     941    umime = _xdg_mime_cache_unalias_mime_type (mime);
     942    ubase = _xdg_mime_cache_unalias_mime_type (base);
     943  
     944    if (strcmp (umime, ubase) == 0)
     945      return 1;
     946  
     947    /* We really want to handle text/ * in GtkFileFilter, so we just
     948     * turn on the supertype matching
     949     */
     950  #if 1
     951    /* Handle supertypes */
     952    if (is_super_type (ubase) &&
     953        xdg_mime_media_type_equal (umime, ubase))
     954      return 1;
     955  #endif
     956  
     957    /*  Handle special cases text/plain and application/octet-stream */
     958    if (strcmp (ubase, "text/plain") == 0 && 
     959        strncmp (umime, "text/", 5) == 0)
     960      return 1;
     961  
     962    if (strcmp (ubase, "application/octet-stream") == 0 &&
     963        strncmp (umime, "inode/", 6) != 0)
     964      return 1;
     965  
     966    if (!seen)
     967      {
     968        first_seen = calloc (1, sizeof (char *));
     969        seen = &first_seen;
     970      }
     971  
     972    for (i = 0; _caches[i]; i++)
     973      {
     974        XdgMimeCache *cache = _caches[i];
     975        xdg_uint32_t list_offset;
     976        xdg_uint32_t n_entries;
     977        xdg_uint32_t offset, n_parents, parent_offset;
     978  
     979        if (cache->buffer == NULL)
     980          continue;
     981  
     982        list_offset = GET_UINT32 (cache->buffer, 8);
     983        if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
     984          continue;
     985  
     986        n_entries = GET_UINT32 (cache->buffer, list_offset);
     987        if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
     988          continue;
     989  
     990        min = 0; 
     991        max = n_entries - 1;
     992        while (max >= min)
     993  	{
     994  	  med = (min + max)/2;
     995  	  
     996  	  offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med);
     997  	  cmp = strcmp (cache->buffer + offset, umime);
     998  	  if (cmp < 0)
     999  	    min = med + 1;
    1000  	  else if (cmp > 0)
    1001  	    max = med - 1;
    1002  	  else
    1003  	    {
    1004  	      offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * med + 4);
    1005  	      n_parents = GET_UINT32 (cache->buffer, offset);
    1006  	      
    1007  	      for (j = 0; j < n_parents; j++)
    1008  		{
    1009  		  parent_offset = GET_UINT32 (cache->buffer, offset + 4 + 4 * j);
    1010  		  parent = cache->buffer + parent_offset;
    1011  
    1012  		  /* Detect and avoid buggy circular relationships */
    1013  		  for (k = 0; (*seen)[k] != NULL; k++)
    1014  		    if (parent == (*seen)[k])
    1015  		      goto next_parent;
    1016  		  new_seen = realloc (*seen, (k + 2) * sizeof (char *));
    1017  		  if (!new_seen)
    1018  		    goto done;
    1019  		  new_seen[k] = parent;
    1020  		  new_seen[k + 1] = NULL;
    1021  		  *seen = new_seen;
    1022  
    1023  		  if (_xdg_mime_cache_mime_type_subclass (parent, ubase, seen))
    1024  		    {
    1025  		      ret = 1;
    1026  		      goto done;
    1027  		    }
    1028  
    1029  		next_parent:
    1030  		  continue;
    1031  		}
    1032  
    1033  	      break;
    1034  	    }
    1035  	}
    1036      }
    1037  
    1038  done:
    1039    free (first_seen);
    1040    return ret;
    1041  }
    1042  
    1043  const char *
    1044  _xdg_mime_cache_unalias_mime_type (const char *mime)
    1045  {
    1046    const char *lookup;
    1047    
    1048    lookup = cache_alias_lookup (mime);
    1049    
    1050    if (lookup)
    1051      return lookup;
    1052    
    1053    return mime;  
    1054  }
    1055  
    1056  char **
    1057  _xdg_mime_cache_list_mime_parents (const char *mime)
    1058  {
    1059    int i, l, p;
    1060    xdg_uint32_t j, k;
    1061    char *all_parents[128]; /* we'll stop at 128 */ 
    1062    char **result;
    1063  
    1064    mime = xdg_mime_unalias_mime_type (mime);
    1065  
    1066    p = 0;
    1067    for (i = 0; _caches[i]; i++)
    1068      {
    1069        XdgMimeCache *cache = _caches[i];
    1070        xdg_uint32_t list_offset;
    1071        xdg_uint32_t n_entries;
    1072  
    1073        if (cache->buffer == NULL)
    1074          continue;
    1075  
    1076        list_offset = GET_UINT32 (cache->buffer, 8);
    1077        if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
    1078          continue;
    1079  
    1080        n_entries = GET_UINT32 (cache->buffer, list_offset);
    1081        if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
    1082          continue;
    1083  
    1084        for (j = 0; j < n_entries; j++)
    1085  	{
    1086  	  xdg_uint32_t mimetype_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j);
    1087  	  xdg_uint32_t parents_offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * j + 4);
    1088  
    1089  	  if (strcmp (cache->buffer + mimetype_offset, mime) == 0)
    1090  	    {
    1091  	      xdg_uint32_t parent_mime_offset;
    1092  	      xdg_uint32_t n_parents = GET_UINT32 (cache->buffer, parents_offset);
    1093  
    1094  	      for (k = 0; k < n_parents && p < 127; k++)
    1095  		{
    1096  		  parent_mime_offset = GET_UINT32 (cache->buffer, parents_offset + 4 + 4 * k);
    1097  
    1098  		  /* Don't add same parent multiple times.
    1099  		   * This can happen for instance if the same type is listed in multiple directories
    1100  		   */
    1101  		  for (l = 0; l < p; l++)
    1102  		    {
    1103  		      if (strcmp (all_parents[l], cache->buffer + parent_mime_offset) == 0)
    1104  			break;
    1105  		    }
    1106  
    1107  		  if (l == p)
    1108  		    all_parents[p++] = cache->buffer + parent_mime_offset;
    1109  		}
    1110  
    1111  	      break;
    1112  	    }
    1113  	}
    1114      }
    1115    all_parents[p++] = NULL;
    1116    
    1117    result = (char **) malloc (p * sizeof (char *));
    1118    memcpy (result, all_parents, p * sizeof (char *));
    1119  
    1120    return result;
    1121  }
    1122  
    1123  static const char *
    1124  cache_lookup_icon (const char *mime, size_t header)
    1125  {
    1126    const char *ptr;
    1127    int i, min, max, mid, cmp;
    1128  
    1129    for (i = 0; _caches[i]; i++)
    1130      {
    1131        XdgMimeCache *cache = _caches[i];
    1132        xdg_uint32_t list_offset;
    1133        xdg_uint32_t n_entries;
    1134        xdg_uint32_t offset;
    1135  
    1136        if (cache->buffer == NULL)
    1137          continue;
    1138  
    1139        if (OUT_OF_BOUNDS (header, 1, 4, cache->size))
    1140          continue;
    1141  
    1142        list_offset = GET_UINT32 (cache->buffer, header);
    1143        if (OUT_OF_BOUNDS (list_offset, 1, 4, cache->size))
    1144          continue;
    1145  
    1146        n_entries = GET_UINT32 (cache->buffer, list_offset);
    1147        if (OUT_OF_BOUNDS (list_offset + 4, n_entries, 8, cache->size))
    1148          continue;
    1149  
    1150        min = 0; 
    1151        max = n_entries - 1;
    1152        while (max >= min) 
    1153          {
    1154            mid = (min + max) / 2;
    1155  
    1156            offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid);
    1157            ptr = cache->buffer + offset;
    1158            cmp = strcmp (ptr, mime);
    1159           
    1160            if (cmp < 0)
    1161              min = mid + 1;
    1162            else if (cmp > 0)
    1163              max = mid - 1;
    1164            else
    1165              {
    1166                offset = GET_UINT32 (cache->buffer, list_offset + 4 + 8 * mid + 4);
    1167                return cache->buffer + offset;
    1168              }
    1169          }
    1170      }
    1171  
    1172    return NULL;
    1173  }
    1174  
    1175  const char *
    1176  _xdg_mime_cache_get_generic_icon (const char *mime)
    1177  {
    1178    return cache_lookup_icon (mime, 36);
    1179  }
    1180  
    1181  const char *
    1182  _xdg_mime_cache_get_icon (const char *mime)
    1183  {
    1184    return cache_lookup_icon (mime, 32);
    1185  }
    1186  
    1187  static void
    1188  dump_glob_node (XdgMimeCache *cache,
    1189  		xdg_uint32_t  offset,
    1190  		int           depth)
    1191  {
    1192    xdg_unichar_t character;
    1193    xdg_uint32_t mime_offset;
    1194    xdg_uint32_t n_children;
    1195    xdg_uint32_t child_offset;
    1196    xdg_uint32_t k;
    1197    int i;
    1198  
    1199    character = GET_UINT32 (cache->buffer, offset);
    1200    mime_offset = GET_UINT32 (cache->buffer, offset + 4);
    1201    n_children = GET_UINT32 (cache->buffer, offset + 8);
    1202    child_offset = GET_UINT32 (cache->buffer, offset + 12);
    1203    if (OUT_OF_BOUNDS (child_offset, n_children, 20, cache->size))
    1204      return;
    1205  
    1206    for (i = 0; i < depth; i++)
    1207      printf (" ");
    1208    printf ("%c", character);
    1209    if (mime_offset)
    1210      printf (" - %s", cache->buffer + mime_offset);
    1211    printf ("\n");
    1212    if (child_offset)
    1213    {
    1214      for (k = 0; k < n_children; k++)
    1215        dump_glob_node (cache, child_offset + 20 * k, depth + 1);
    1216    }
    1217  }
    1218  
    1219  void
    1220  _xdg_mime_cache_glob_dump (void)
    1221  {
    1222    xdg_uint32_t i, j;
    1223    for (i = 0; _caches[i]; i++)
    1224    {
    1225      XdgMimeCache *cache = _caches[i];
    1226      xdg_uint32_t list_offset;
    1227      xdg_uint32_t n_entries;
    1228      xdg_uint32_t offset;
    1229  
    1230      if (cache->buffer == NULL)
    1231        continue;
    1232  
    1233      list_offset = GET_UINT32 (cache->buffer, 16);
    1234      if (OUT_OF_BOUNDS (list_offset, 1, 8, cache->size))
    1235        return;
    1236  
    1237      n_entries = GET_UINT32 (cache->buffer, list_offset);
    1238      offset = GET_UINT32 (cache->buffer, list_offset + 4);
    1239      if (OUT_OF_BOUNDS (offset, n_entries, 20, cache->size))
    1240        return;
    1241  
    1242      for (j = 0; j < n_entries; j++)
    1243  	    dump_glob_node (cache, offset + 20 * j, 0);
    1244    }
    1245  }
    1246  
    1247