| /* -*- mode: C; c-file-style: "gnu" -*- */ | 
 | /* xdgmimemagic.: Private file.  Datastructure for storing magic files. | 
 |  * | 
 |  * More info can be found at http://www.freedesktop.org/standards/ | 
 |  * | 
 |  * Copyright (C) 2003  Red Hat, Inc. | 
 |  * Copyright (C) 2003  Jonathan Blandford <jrb@alum.mit.edu> | 
 |  * | 
 |  * Licensed under the Academic Free License version 2.0 | 
 |  * Or under the following terms: | 
 |  * | 
 |  * This library is free software; you can redistribute it and/or | 
 |  * modify it under the terms of the GNU Lesser General Public | 
 |  * License as published by the Free Software Foundation; either | 
 |  * version 2 of the License, or (at your option) any later version. | 
 |  * | 
 |  * This library is distributed in the hope that it will be useful, | 
 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU | 
 |  * Lesser General Public License for more details. | 
 |  * | 
 |  * You should have received a copy of the GNU Lesser General Public | 
 |  * License along with this library; if not, write to the | 
 |  * Free Software Foundation, Inc., 59 Temple Place - Suite 330, | 
 |  * Boston, MA 02111-1307, USA. | 
 |  */ | 
 |  | 
 | #ifdef HAVE_CONFIG_H | 
 | #include <config.h> | 
 | #endif | 
 |  | 
 | #include <assert.h> | 
 | #include "xdgmimemagic.h" | 
 | #include "xdgmimeint.h" | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <ctype.h> | 
 | #include <errno.h> | 
 | #include <limits.h> | 
 |  | 
 | #ifndef	FALSE | 
 | #define	FALSE	(0) | 
 | #endif | 
 |  | 
 | #ifndef	TRUE | 
 | #define	TRUE	(!FALSE) | 
 | #endif | 
 |  | 
 | #if !defined getc_unlocked && !defined HAVE_GETC_UNLOCKED | 
 | # define getc_unlocked(fp) getc (fp) | 
 | #endif | 
 |  | 
 | typedef struct XdgMimeMagicMatch XdgMimeMagicMatch; | 
 | typedef struct XdgMimeMagicMatchlet XdgMimeMagicMatchlet; | 
 |  | 
 | typedef enum | 
 | { | 
 |   XDG_MIME_MAGIC_SECTION, | 
 |   XDG_MIME_MAGIC_MAGIC, | 
 |   XDG_MIME_MAGIC_ERROR, | 
 |   XDG_MIME_MAGIC_EOF | 
 | } XdgMimeMagicState; | 
 |  | 
 | struct XdgMimeMagicMatch | 
 | { | 
 |   const char *mime_type; | 
 |   int priority; | 
 |   XdgMimeMagicMatchlet *matchlet; | 
 |   XdgMimeMagicMatch *next; | 
 | }; | 
 |  | 
 |  | 
 | struct XdgMimeMagicMatchlet | 
 | { | 
 |   int indent; | 
 |   int offset; | 
 |   unsigned int value_length; | 
 |   unsigned char *value; | 
 |   unsigned char *mask; | 
 |   unsigned int range_length; | 
 |   unsigned int word_size; | 
 |   XdgMimeMagicMatchlet *next; | 
 | }; | 
 |  | 
 |  | 
 | struct XdgMimeMagic | 
 | { | 
 |   XdgMimeMagicMatch *match_list; | 
 |   int max_extent; | 
 | }; | 
 |  | 
 | static XdgMimeMagicMatch * | 
 | _xdg_mime_magic_match_new (void) | 
 | { | 
 |   return calloc (1, sizeof (XdgMimeMagicMatch)); | 
 | } | 
 |  | 
 |  | 
 | static XdgMimeMagicMatchlet * | 
 | _xdg_mime_magic_matchlet_new (void) | 
 | { | 
 |   XdgMimeMagicMatchlet *matchlet; | 
 |  | 
 |   matchlet = malloc (sizeof (XdgMimeMagicMatchlet)); | 
 |  | 
 |   matchlet->indent = 0; | 
 |   matchlet->offset = 0; | 
 |   matchlet->value_length = 0; | 
 |   matchlet->value = NULL; | 
 |   matchlet->mask = NULL; | 
 |   matchlet->range_length = 1; | 
 |   matchlet->word_size = 1; | 
 |   matchlet->next = NULL; | 
 |  | 
 |   return matchlet; | 
 | } | 
 |  | 
 |  | 
 | static void | 
 | _xdg_mime_magic_matchlet_free (XdgMimeMagicMatchlet *mime_magic_matchlet) | 
 | { | 
 |   if (mime_magic_matchlet) | 
 |     { | 
 |       if (mime_magic_matchlet->next) | 
 | 	_xdg_mime_magic_matchlet_free (mime_magic_matchlet->next); | 
 |       if (mime_magic_matchlet->value) | 
 | 	free (mime_magic_matchlet->value); | 
 |       if (mime_magic_matchlet->mask) | 
 | 	free (mime_magic_matchlet->mask); | 
 |       free (mime_magic_matchlet); | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | /* Frees mime_magic_match and the remainder of its list | 
 |  */ | 
 | static void | 
 | _xdg_mime_magic_match_free (XdgMimeMagicMatch *mime_magic_match) | 
 | { | 
 |   XdgMimeMagicMatch *ptr, *next; | 
 |  | 
 |   ptr = mime_magic_match; | 
 |   while (ptr) | 
 |     { | 
 |       next = ptr->next; | 
 |  | 
 |       if (ptr->mime_type) | 
 | 	free ((void *) ptr->mime_type); | 
 |       if (ptr->matchlet) | 
 | 	_xdg_mime_magic_matchlet_free (ptr->matchlet); | 
 |       free (ptr); | 
 |  | 
 |       ptr = next; | 
 |     } | 
 | } | 
 |  | 
 | /* Reads in a hunk of data until a newline character or a '\000' is hit.  The | 
 |  * returned string is null terminated, and doesn't include the newline. | 
 |  */ | 
 | static unsigned char * | 
 | _xdg_mime_magic_read_to_newline (FILE *magic_file, | 
 | 				 int  *end_of_file) | 
 | { | 
 |   unsigned char *retval; | 
 |   int c; | 
 |   int len, pos; | 
 |  | 
 |   len = 128; | 
 |   pos = 0; | 
 |   retval = malloc (len); | 
 |   *end_of_file = FALSE; | 
 |  | 
 |   while (TRUE) | 
 |     { | 
 |       c = getc_unlocked (magic_file); | 
 |       if (c == EOF) | 
 | 	{ | 
 | 	  *end_of_file = TRUE; | 
 | 	  break; | 
 | 	} | 
 |       if (c == '\n' || c == '\000') | 
 | 	break; | 
 |       retval[pos++] = (unsigned char) c; | 
 |       if (pos % 128 == 127) | 
 | 	{ | 
 | 	  len = len + 128; | 
 | 	  retval = realloc (retval, len); | 
 | 	} | 
 |     } | 
 |  | 
 |   retval[pos] = '\000'; | 
 |   return retval; | 
 | } | 
 |  | 
 | /* Returns the number read from the file, or -1 if no number could be read. | 
 |  */ | 
 | static int | 
 | _xdg_mime_magic_read_a_number (FILE *magic_file, | 
 | 			       int  *end_of_file) | 
 | { | 
 |   /* LONG_MAX is about 20 characters on my system */ | 
 | #define MAX_NUMBER_SIZE 30 | 
 |   char number_string[MAX_NUMBER_SIZE + 1]; | 
 |   int pos = 0; | 
 |   int c; | 
 |   long retval = -1; | 
 |  | 
 |   while (TRUE) | 
 |     { | 
 |       c = getc_unlocked (magic_file); | 
 |  | 
 |       if (c == EOF) | 
 | 	{ | 
 | 	  *end_of_file = TRUE; | 
 | 	  break; | 
 | 	} | 
 |       if (! isdigit (c)) | 
 | 	{ | 
 | 	  ungetc (c, magic_file); | 
 | 	  break; | 
 | 	} | 
 |       number_string[pos] = (char) c; | 
 |       pos++; | 
 |       if (pos == MAX_NUMBER_SIZE) | 
 | 	break; | 
 |     } | 
 |   if (pos > 0) | 
 |     { | 
 |       number_string[pos] = '\000'; | 
 |       errno = 0; | 
 |       retval = strtol (number_string, NULL, 10); | 
 |  | 
 |       if ((retval < INT_MIN) || (retval > INT_MAX) || (errno != 0)) | 
 | 	return -1; | 
 |     } | 
 |  | 
 |   return retval; | 
 | } | 
 |  | 
 | /* Headers are of the format: | 
 |  * [<priority>:<mime-type>] | 
 |  */ | 
 | static XdgMimeMagicState | 
 | _xdg_mime_magic_parse_header (FILE *magic_file, XdgMimeMagicMatch *match) | 
 | { | 
 |   int c; | 
 |   char *buffer; | 
 |   char *end_ptr; | 
 |   int end_of_file = 0; | 
 |  | 
 |   assert (magic_file != NULL); | 
 |   assert (match != NULL); | 
 |  | 
 |   c = getc_unlocked (magic_file); | 
 |   if (c == EOF) | 
 |     return XDG_MIME_MAGIC_EOF; | 
 |   if (c != '[') | 
 |     return XDG_MIME_MAGIC_ERROR; | 
 |  | 
 |   match->priority = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); | 
 |   if (end_of_file) | 
 |     return XDG_MIME_MAGIC_EOF; | 
 |   if (match->priority == -1) | 
 |     return XDG_MIME_MAGIC_ERROR; | 
 |  | 
 |   c = getc_unlocked (magic_file); | 
 |   if (c == EOF) | 
 |     return XDG_MIME_MAGIC_EOF; | 
 |   if (c != ':') | 
 |     return XDG_MIME_MAGIC_ERROR; | 
 |  | 
 |   buffer = (char *)_xdg_mime_magic_read_to_newline (magic_file, &end_of_file); | 
 |   if (end_of_file) | 
 |     return XDG_MIME_MAGIC_EOF; | 
 |  | 
 |   end_ptr = buffer; | 
 |   while (*end_ptr != ']' && *end_ptr != '\000' && *end_ptr != '\n') | 
 |     end_ptr++; | 
 |   if (*end_ptr != ']') | 
 |     { | 
 |       free (buffer); | 
 |       return XDG_MIME_MAGIC_ERROR; | 
 |     } | 
 |   *end_ptr = '\000'; | 
 |  | 
 |   match->mime_type = strdup (buffer); | 
 |   free (buffer); | 
 |  | 
 |   return XDG_MIME_MAGIC_MAGIC; | 
 | } | 
 |  | 
 | static XdgMimeMagicState | 
 | _xdg_mime_magic_parse_error (FILE *magic_file) | 
 | { | 
 |   int c; | 
 |  | 
 |   while (1) | 
 |     { | 
 |       c = getc_unlocked (magic_file); | 
 |       if (c == EOF) | 
 | 	return XDG_MIME_MAGIC_EOF; | 
 |       if (c == '\n') | 
 | 	return XDG_MIME_MAGIC_SECTION; | 
 |     } | 
 | } | 
 |  | 
 | /* Headers are of the format: | 
 |  * [ indent ] ">" start-offset "=" value | 
 |  * [ "&" mask ] [ "~" word-size ] [ "+" range-length ] "\n" | 
 |  */ | 
 | static XdgMimeMagicState | 
 | _xdg_mime_magic_parse_magic_line (FILE              *magic_file, | 
 | 				  XdgMimeMagicMatch *match) | 
 | { | 
 |   XdgMimeMagicMatchlet *matchlet; | 
 |   int c; | 
 |   int end_of_file; | 
 |   int indent = 0; | 
 |   int bytes_read; | 
 |  | 
 |   assert (magic_file != NULL); | 
 |  | 
 |   /* Sniff the buffer to make sure it's a valid line */ | 
 |   c = getc_unlocked (magic_file); | 
 |   if (c == EOF) | 
 |     return XDG_MIME_MAGIC_EOF; | 
 |   else if (c == '[') | 
 |     { | 
 |       ungetc (c, magic_file); | 
 |       return XDG_MIME_MAGIC_SECTION; | 
 |     } | 
 |   else if (c == '\n') | 
 |     return XDG_MIME_MAGIC_MAGIC; | 
 |  | 
 |   /* At this point, it must be a digit or a '>' */ | 
 |   end_of_file = FALSE; | 
 |   if (isdigit (c)) | 
 |     { | 
 |       ungetc (c, magic_file); | 
 |       indent = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); | 
 |       if (end_of_file) | 
 | 	return XDG_MIME_MAGIC_EOF; | 
 |       if (indent == -1) | 
 | 	return XDG_MIME_MAGIC_ERROR; | 
 |       c = getc_unlocked (magic_file); | 
 |       if (c == EOF) | 
 | 	return XDG_MIME_MAGIC_EOF; | 
 |     } | 
 |  | 
 |   if (c != '>') | 
 |     return XDG_MIME_MAGIC_ERROR; | 
 |  | 
 |   matchlet = _xdg_mime_magic_matchlet_new (); | 
 |   matchlet->indent = indent; | 
 |   matchlet->offset = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); | 
 |   if (end_of_file) | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       return XDG_MIME_MAGIC_EOF; | 
 |     } | 
 |   if (matchlet->offset == -1) | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       return XDG_MIME_MAGIC_ERROR; | 
 |     } | 
 |   c = getc_unlocked (magic_file); | 
 |   if (c == EOF) | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       return XDG_MIME_MAGIC_EOF; | 
 |     } | 
 |   else if (c != '=') | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       return XDG_MIME_MAGIC_ERROR; | 
 |     } | 
 |  | 
 |   /* Next two bytes determine how long the value is */ | 
 |   matchlet->value_length = 0; | 
 |   c = getc_unlocked (magic_file); | 
 |   if (c == EOF) | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       return XDG_MIME_MAGIC_EOF; | 
 |     } | 
 |   matchlet->value_length = c & 0xFF; | 
 |   matchlet->value_length = matchlet->value_length << 8; | 
 |  | 
 |   c = getc_unlocked (magic_file); | 
 |   if (c == EOF) | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       return XDG_MIME_MAGIC_EOF; | 
 |     } | 
 |   matchlet->value_length = matchlet->value_length + (c & 0xFF); | 
 |  | 
 |   matchlet->value = malloc (matchlet->value_length); | 
 |  | 
 |   /* OOM */ | 
 |   if (matchlet->value == NULL) | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       return XDG_MIME_MAGIC_ERROR; | 
 |     } | 
 |   bytes_read = fread (matchlet->value, 1, matchlet->value_length, magic_file); | 
 |   if (bytes_read != matchlet->value_length) | 
 |     { | 
 |       _xdg_mime_magic_matchlet_free (matchlet); | 
 |       if (feof (magic_file)) | 
 | 	return XDG_MIME_MAGIC_EOF; | 
 |       else | 
 | 	return XDG_MIME_MAGIC_ERROR; | 
 |     } | 
 |  | 
 |   c = getc_unlocked (magic_file); | 
 |   if (c == '&') | 
 |     { | 
 |       matchlet->mask = malloc (matchlet->value_length); | 
 |       /* OOM */ | 
 |       if (matchlet->mask == NULL) | 
 | 	{ | 
 | 	  _xdg_mime_magic_matchlet_free (matchlet); | 
 | 	  return XDG_MIME_MAGIC_ERROR; | 
 | 	} | 
 |       bytes_read = fread (matchlet->mask, 1, matchlet->value_length, magic_file); | 
 |       if (bytes_read != matchlet->value_length) | 
 | 	{ | 
 | 	  _xdg_mime_magic_matchlet_free (matchlet); | 
 | 	  if (feof (magic_file)) | 
 | 	    return XDG_MIME_MAGIC_EOF; | 
 | 	  else | 
 | 	    return XDG_MIME_MAGIC_ERROR; | 
 | 	} | 
 |       c = getc_unlocked (magic_file); | 
 |     } | 
 |  | 
 |   if (c == '~') | 
 |     { | 
 |       matchlet->word_size = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); | 
 |       if (end_of_file) | 
 | 	{ | 
 | 	  _xdg_mime_magic_matchlet_free (matchlet); | 
 | 	  return XDG_MIME_MAGIC_EOF; | 
 | 	} | 
 |       if (matchlet->word_size != 0 && | 
 | 	  matchlet->word_size != 1 && | 
 | 	  matchlet->word_size != 2 && | 
 | 	  matchlet->word_size != 4) | 
 | 	{ | 
 | 	  _xdg_mime_magic_matchlet_free (matchlet); | 
 | 	  return XDG_MIME_MAGIC_ERROR; | 
 | 	} | 
 |       c = getc_unlocked (magic_file); | 
 |     } | 
 |  | 
 |   if (c == '+') | 
 |     { | 
 |       matchlet->range_length = _xdg_mime_magic_read_a_number (magic_file, &end_of_file); | 
 |       if (end_of_file) | 
 | 	{ | 
 | 	  _xdg_mime_magic_matchlet_free (matchlet); | 
 | 	  return XDG_MIME_MAGIC_EOF; | 
 | 	} | 
 |       if (matchlet->range_length == -1) | 
 | 	{ | 
 | 	  _xdg_mime_magic_matchlet_free (matchlet); | 
 | 	  return XDG_MIME_MAGIC_ERROR; | 
 | 	} | 
 |       c = getc_unlocked (magic_file); | 
 |     } | 
 |  | 
 |  | 
 |   if (c == '\n') | 
 |     { | 
 |       /* We clean up the matchlet, byte swapping if needed */ | 
 |       if (matchlet->word_size > 1) | 
 | 	{ | 
 | 	  int i; | 
 | 	  if (matchlet->value_length % matchlet->word_size != 0) | 
 | 	    { | 
 | 	      _xdg_mime_magic_matchlet_free (matchlet); | 
 | 	      return XDG_MIME_MAGIC_ERROR; | 
 | 	    } | 
 | 	  /* FIXME: need to get this defined in a <config.h> style file */ | 
 | #if LITTLE_ENDIAN | 
 | 	  for (i = 0; i < matchlet->value_length; i = i + matchlet->word_size) | 
 | 	    { | 
 | 	      if (matchlet->word_size == 2) | 
 | 		*((xdg_uint16_t *) matchlet->value + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->value + i))); | 
 | 	      else if (matchlet->word_size == 4) | 
 | 		*((xdg_uint32_t *) matchlet->value + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->value + i))); | 
 | 	      if (matchlet->mask) | 
 | 		{ | 
 | 		  if (matchlet->word_size == 2) | 
 | 		    *((xdg_uint16_t *) matchlet->mask + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->mask + i))); | 
 | 		  else if (matchlet->word_size == 4) | 
 | 		    *((xdg_uint32_t *) matchlet->mask + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->mask + i))); | 
 |  | 
 | 		} | 
 | 	    } | 
 | #endif | 
 | 	} | 
 |  | 
 |       matchlet->next = match->matchlet; | 
 |       match->matchlet = matchlet; | 
 |  | 
 |  | 
 |       return XDG_MIME_MAGIC_MAGIC; | 
 |     } | 
 |  | 
 |   _xdg_mime_magic_matchlet_free (matchlet); | 
 |   if (c == EOF) | 
 |     return XDG_MIME_MAGIC_EOF; | 
 |  | 
 |   return XDG_MIME_MAGIC_ERROR; | 
 | } | 
 |  | 
 | static int | 
 | _xdg_mime_magic_matchlet_compare_to_data (XdgMimeMagicMatchlet *matchlet, | 
 | 					  const void           *data, | 
 | 					  size_t                len) | 
 | { | 
 |   int i, j; | 
 |   for (i = matchlet->offset; i < matchlet->offset + matchlet->range_length; i++) | 
 |     { | 
 |       int valid_matchlet = TRUE; | 
 |  | 
 |       if (i + matchlet->value_length > len) | 
 | 	return FALSE; | 
 |  | 
 |       if (matchlet->mask) | 
 | 	{ | 
 | 	  for (j = 0; j < matchlet->value_length; j++) | 
 | 	    { | 
 | 	      if ((matchlet->value[j] & matchlet->mask[j]) != | 
 | 		  ((((unsigned char *) data)[j + i]) & matchlet->mask[j])) | 
 | 		{ | 
 | 		  valid_matchlet = FALSE; | 
 | 		  break; | 
 | 		} | 
 | 	    } | 
 | 	} | 
 |       else | 
 | 	{ | 
 | 	  for (j = 0; j <  matchlet->value_length; j++) | 
 | 	    { | 
 | 	      if (matchlet->value[j] != ((unsigned char *) data)[j + i]) | 
 | 		{ | 
 | 		  valid_matchlet = FALSE; | 
 | 		  break; | 
 | 		} | 
 | 	    } | 
 | 	} | 
 |       if (valid_matchlet) | 
 | 	return TRUE; | 
 |     } | 
 |   return FALSE; | 
 | } | 
 |  | 
 | static int | 
 | _xdg_mime_magic_matchlet_compare_level (XdgMimeMagicMatchlet *matchlet, | 
 | 					const void           *data, | 
 | 					size_t                len, | 
 | 					int                   indent) | 
 | { | 
 |   while ((matchlet != NULL) && (matchlet->indent == indent)) | 
 |     { | 
 |       if (_xdg_mime_magic_matchlet_compare_to_data (matchlet, data, len)) | 
 | 	{ | 
 | 	  if ((matchlet->next == NULL) || | 
 | 	      (matchlet->next->indent <= indent)) | 
 | 	    return TRUE; | 
 |  | 
 | 	  if (_xdg_mime_magic_matchlet_compare_level (matchlet->next, | 
 | 						      data, | 
 | 						      len, | 
 | 						      indent + 1)) | 
 | 	    return TRUE; | 
 | 	} | 
 |  | 
 |       do | 
 | 	{ | 
 | 	  matchlet = matchlet->next; | 
 | 	} | 
 |       while (matchlet && matchlet->indent > indent); | 
 |     } | 
 |  | 
 |   return FALSE; | 
 | } | 
 |  | 
 | static int | 
 | _xdg_mime_magic_match_compare_to_data (XdgMimeMagicMatch *match, | 
 | 				       const void        *data, | 
 | 				       size_t             len) | 
 | { | 
 |   return _xdg_mime_magic_matchlet_compare_level (match->matchlet, data, len, 0); | 
 | } | 
 |  | 
 | static void | 
 | _xdg_mime_magic_insert_match (XdgMimeMagic      *mime_magic, | 
 | 			      XdgMimeMagicMatch *match) | 
 | { | 
 |   XdgMimeMagicMatch *list; | 
 |  | 
 |   if (mime_magic->match_list == NULL) | 
 |     { | 
 |       mime_magic->match_list = match; | 
 |       return; | 
 |     } | 
 |  | 
 |   if (match->priority > mime_magic->match_list->priority) | 
 |     { | 
 |       match->next = mime_magic->match_list; | 
 |       mime_magic->match_list = match; | 
 |       return; | 
 |     } | 
 |  | 
 |   list = mime_magic->match_list; | 
 |   while (list->next != NULL) | 
 |     { | 
 |       if (list->next->priority < match->priority) | 
 | 	{ | 
 | 	  match->next = list->next; | 
 | 	  list->next = match; | 
 | 	  return; | 
 | 	} | 
 |       list = list->next; | 
 |     } | 
 |   list->next = match; | 
 |   match->next = NULL; | 
 | } | 
 |  | 
 | XdgMimeMagic * | 
 | _xdg_mime_magic_new (void) | 
 | { | 
 |   return calloc (1, sizeof (XdgMimeMagic)); | 
 | } | 
 |  | 
 | void | 
 | _xdg_mime_magic_free (XdgMimeMagic *mime_magic) | 
 | { | 
 |   if (mime_magic) { | 
 |     _xdg_mime_magic_match_free (mime_magic->match_list); | 
 |     free (mime_magic); | 
 |   } | 
 | } | 
 |  | 
 | int | 
 | _xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic) | 
 | { | 
 |   return mime_magic->max_extent; | 
 | } | 
 |  | 
 | const char * | 
 | _xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic, | 
 | 			     const void   *data, | 
 | 			     size_t        len, | 
 | 			     int           *result_prio, | 
 |                              const char   *mime_types[], | 
 |                              int           n_mime_types) | 
 | { | 
 |   XdgMimeMagicMatch *match; | 
 |   const char *mime_type; | 
 |   int n; | 
 |   int prio; | 
 |  | 
 |   prio = 0; | 
 |   mime_type = NULL; | 
 |   for (match = mime_magic->match_list; match; match = match->next) | 
 |     { | 
 |       if (_xdg_mime_magic_match_compare_to_data (match, data, len)) | 
 | 	{ | 
 | 	  prio = match->priority; | 
 | 	  mime_type = match->mime_type; | 
 | 	  break; | 
 | 	} | 
 |       else  | 
 | 	{ | 
 | 	  for (n = 0; n < n_mime_types; n++) | 
 | 	    { | 
 | 	      if (mime_types[n] &&  | 
 | 		  _xdg_mime_mime_type_equal (mime_types[n], match->mime_type)) | 
 | 		mime_types[n] = NULL; | 
 | 	    } | 
 | 	} | 
 |     } | 
 |  | 
 |   if (mime_type == NULL) | 
 |     { | 
 |       for (n = 0; n < n_mime_types; n++) | 
 | 	{ | 
 | 	  if (mime_types[n]) | 
 | 	    mime_type = mime_types[n]; | 
 | 	} | 
 |     } | 
 |    | 
 |   if (result_prio) | 
 |     *result_prio = prio; | 
 |  | 
 |   return mime_type; | 
 | } | 
 |  | 
 | static void | 
 | _xdg_mime_update_mime_magic_extents (XdgMimeMagic *mime_magic) | 
 | { | 
 |   XdgMimeMagicMatch *match; | 
 |   int max_extent = 0; | 
 |  | 
 |   for (match = mime_magic->match_list; match; match = match->next) | 
 |     { | 
 |       XdgMimeMagicMatchlet *matchlet; | 
 |  | 
 |       for (matchlet = match->matchlet; matchlet; matchlet = matchlet->next) | 
 | 	{ | 
 | 	  int extent; | 
 |  | 
 | 	  extent = matchlet->value_length + matchlet->offset + matchlet->range_length; | 
 | 	  if (max_extent < extent) | 
 | 	    max_extent = extent; | 
 | 	} | 
 |     } | 
 |  | 
 |   mime_magic->max_extent = max_extent; | 
 | } | 
 |  | 
 | static XdgMimeMagicMatchlet * | 
 | _xdg_mime_magic_matchlet_mirror (XdgMimeMagicMatchlet *matchlets) | 
 | { | 
 |   XdgMimeMagicMatchlet *new_list; | 
 |   XdgMimeMagicMatchlet *tmp; | 
 |  | 
 |   if ((matchlets == NULL) || (matchlets->next == NULL)) | 
 |     return matchlets; | 
 |  | 
 |   new_list = NULL; | 
 |   tmp = matchlets; | 
 |   while (tmp != NULL) | 
 |     { | 
 |       XdgMimeMagicMatchlet *matchlet; | 
 |  | 
 |       matchlet = tmp; | 
 |       tmp = tmp->next; | 
 |       matchlet->next = new_list; | 
 |       new_list = matchlet; | 
 |     } | 
 |  | 
 |   return new_list; | 
 |  | 
 | } | 
 |  | 
 | static void | 
 | _xdg_mime_magic_read_magic_file (XdgMimeMagic *mime_magic, | 
 | 				 FILE         *magic_file) | 
 | { | 
 |   XdgMimeMagicState state; | 
 |   XdgMimeMagicMatch *match = NULL; /* Quiet compiler */ | 
 |  | 
 |   state = XDG_MIME_MAGIC_SECTION; | 
 |  | 
 |   while (state != XDG_MIME_MAGIC_EOF) | 
 |     { | 
 |       switch (state) | 
 | 	{ | 
 | 	case XDG_MIME_MAGIC_SECTION: | 
 | 	  match = _xdg_mime_magic_match_new (); | 
 | 	  state = _xdg_mime_magic_parse_header (magic_file, match); | 
 | 	  if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR) | 
 | 	    _xdg_mime_magic_match_free (match); | 
 | 	  break; | 
 | 	case XDG_MIME_MAGIC_MAGIC: | 
 | 	  state = _xdg_mime_magic_parse_magic_line (magic_file, match); | 
 | 	  if (state == XDG_MIME_MAGIC_SECTION || | 
 | 	      (state == XDG_MIME_MAGIC_EOF && match->mime_type)) | 
 | 	    { | 
 | 	      match->matchlet = _xdg_mime_magic_matchlet_mirror (match->matchlet); | 
 | 	      _xdg_mime_magic_insert_match (mime_magic, match); | 
 | 	    } | 
 | 	  else if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR) | 
 | 	    _xdg_mime_magic_match_free (match); | 
 | 	  break; | 
 | 	case XDG_MIME_MAGIC_ERROR: | 
 | 	  state = _xdg_mime_magic_parse_error (magic_file); | 
 | 	  break; | 
 | 	case XDG_MIME_MAGIC_EOF: | 
 | 	default: | 
 | 	  /* Make the compiler happy */ | 
 | 	  assert (0); | 
 | 	} | 
 |     } | 
 |   _xdg_mime_update_mime_magic_extents (mime_magic); | 
 | } | 
 |  | 
 | void | 
 | _xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic, | 
 | 				const char   *file_name) | 
 | { | 
 |   FILE *magic_file; | 
 |   char header[12]; | 
 |  | 
 |   magic_file = fopen (file_name, "r"); | 
 |  | 
 |   if (magic_file == NULL) | 
 |     return; | 
 |  | 
 |   if (fread (header, 1, 12, magic_file) == 12) | 
 |     { | 
 |       if (memcmp ("MIME-Magic\0\n", header, 12) == 0) | 
 |         _xdg_mime_magic_read_magic_file (mime_magic, magic_file); | 
 |     } | 
 |  | 
 |   fclose (magic_file); | 
 | } |