]> Pileus Git - ~andy/gtk/blob - gtk/xdgmime/xdgmimemagic.c
Fixes #136082 and #135265, patch by Morten Welinder.
[~andy/gtk] / gtk / xdgmime / xdgmimemagic.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* xdgmimemagic.: Private file.  Datastructure for storing magic files.
3  *
4  * More info can be found at http://www.freedesktop.org/standards/
5  *
6  * Copyright (C) 2003  Red Hat, Inc.
7  * Copyright (C) 2003  Jonathan Blandford <jrb@alum.mit.edu>
8  *
9  * Licensed under the Academic Free License version 2.0
10  * Or under the following terms:
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28 #include <config.h>
29 #include <assert.h>
30 #include "xdgmimemagic.h"
31 #include "xdgmimeint.h"
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <limits.h>
38
39 #ifndef FALSE
40 #define FALSE   (0)
41 #endif
42
43 #ifndef TRUE
44 #define TRUE    (!FALSE)
45 #endif
46
47 extern int errno;
48
49 typedef struct XdgMimeMagicMatch XdgMimeMagicMatch;
50 typedef struct XdgMimeMagicMatchlet XdgMimeMagicMatchlet;
51
52 typedef enum
53 {
54   XDG_MIME_MAGIC_SECTION,
55   XDG_MIME_MAGIC_MAGIC,
56   XDG_MIME_MAGIC_ERROR,
57   XDG_MIME_MAGIC_EOF
58 } XdgMimeMagicState;
59
60 struct XdgMimeMagicMatch
61 {
62   const char *mime_type;
63   int priority;
64   XdgMimeMagicMatchlet *matchlet;
65   XdgMimeMagicMatch *next;
66 };
67
68
69 struct XdgMimeMagicMatchlet
70 {
71   int indent;
72   int offset;
73   unsigned int value_length;
74   unsigned char *value;
75   unsigned char *mask;
76   unsigned int range_length;
77   unsigned int word_size;
78   XdgMimeMagicMatchlet *next;
79 };
80
81
82 struct XdgMimeMagic
83 {
84   XdgMimeMagicMatch *match_list;
85   int max_extent;
86 };
87
88 static XdgMimeMagicMatch *
89 _xdg_mime_magic_match_new (void)
90 {
91   return calloc (1, sizeof (XdgMimeMagicMatch));
92 }
93
94
95 static XdgMimeMagicMatchlet *
96 _xdg_mime_magic_matchlet_new (void)
97 {
98   XdgMimeMagicMatchlet *matchlet;
99
100   matchlet = malloc (sizeof (XdgMimeMagicMatchlet));
101
102   matchlet->indent = 0;
103   matchlet->offset = 0;
104   matchlet->value_length = 0;
105   matchlet->value = NULL;
106   matchlet->mask = NULL;
107   matchlet->range_length = 1;
108   matchlet->word_size = 1;
109   matchlet->next = NULL;
110
111   return matchlet;
112 }
113
114
115 static void
116 _xdg_mime_magic_matchlet_free (XdgMimeMagicMatchlet *mime_magic_matchlet)
117 {
118   if (mime_magic_matchlet)
119     {
120       if (mime_magic_matchlet->next)
121         _xdg_mime_magic_matchlet_free (mime_magic_matchlet->next);
122       if (mime_magic_matchlet->value)
123         free (mime_magic_matchlet->value);
124       if (mime_magic_matchlet->mask)
125         free (mime_magic_matchlet->mask);
126       free (mime_magic_matchlet);
127     }
128 }
129
130
131 /* Frees mime_magic_match and the remainder of its list
132  */
133 static void
134 _xdg_mime_magic_match_free (XdgMimeMagicMatch *mime_magic_match)
135 {
136   XdgMimeMagicMatch *ptr, *next;
137
138   ptr = mime_magic_match;
139   while (ptr)
140     {
141       next = ptr->next;
142
143       if (ptr->mime_type)
144         free ((void *) ptr->mime_type);
145       if (ptr->matchlet)
146         _xdg_mime_magic_matchlet_free (ptr->matchlet);
147       free (ptr);
148
149       ptr = next;
150     }
151 }
152
153 /* Reads in a hunk of data until a newline character or a '\000' is hit.  The
154  * returned string is null terminated, and doesn't include the newline.
155  */
156 static unsigned char *
157 _xdg_mime_magic_read_to_newline (FILE *magic_file,
158                                  int  *end_of_file)
159 {
160   unsigned char *retval;
161   int c;
162   int len, pos;
163
164   len = 128;
165   pos = 0;
166   retval = malloc (len);
167   *end_of_file = FALSE;
168
169   while (TRUE)
170     {
171       c = fgetc (magic_file);
172       if (c == EOF)
173         {
174           *end_of_file = TRUE;
175           break;
176         }
177       if (c == '\n' || c == '\000')
178         break;
179       retval[pos++] = (unsigned char) c;
180       if (pos % 128 == 127)
181         {
182           len = len + 128;
183           retval = realloc (retval, len);
184         }
185     }
186
187   retval[pos] = '\000';
188   return retval;
189 }
190
191 /* Returns the number read from the file, or -1 if no number could be read.
192  */
193 static int
194 _xdg_mime_magic_read_a_number (FILE *magic_file,
195                                int  *end_of_file)
196 {
197   /* LONG_MAX is about 20 characters on my system */
198 #define MAX_NUMBER_SIZE 30
199   char number_string[MAX_NUMBER_SIZE];
200   int pos = 0;
201   int c;
202   long retval = -1;
203
204   while (TRUE)
205     {
206       c = fgetc (magic_file);
207
208       if (c == EOF)
209         {
210           *end_of_file = TRUE;
211           break;
212         }
213       if (! isdigit ((char) c))
214         {
215           ungetc (c, magic_file);
216           break;
217         }
218       number_string[pos] = (char) c;
219       pos++;
220       if (pos == MAX_NUMBER_SIZE)
221         break;
222     }
223   if (pos > 0)
224     {
225       number_string[pos] = '\000';
226       errno = 0;
227       retval = strtol (number_string, NULL, 10);
228
229       if ((retval < INT_MIN) || (retval > INT_MAX) || (errno != 0))
230         return -1;
231     }
232
233   return retval;
234 }
235
236 /* Headers are of the format:
237  * [<priority>:<mime-type>]
238  */
239 static XdgMimeMagicState
240 _xdg_mime_magic_parse_header (FILE *magic_file, XdgMimeMagicMatch *match)
241 {
242   int c;
243   char *buffer;
244   char *end_ptr;
245   int end_of_file = 0;
246
247   assert (magic_file != NULL);
248   assert (match != NULL);
249
250   c = fgetc (magic_file);
251   if (c == EOF)
252     return XDG_MIME_MAGIC_EOF;
253   if (c != '[')
254     return XDG_MIME_MAGIC_ERROR;
255
256   match->priority = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
257   if (end_of_file)
258     return XDG_MIME_MAGIC_EOF;
259   if (match->priority == -1)
260     return XDG_MIME_MAGIC_ERROR;
261
262   c = fgetc (magic_file);
263   if (c == EOF)
264     return XDG_MIME_MAGIC_EOF;
265   if (c != ':')
266     return XDG_MIME_MAGIC_ERROR;
267
268   buffer = _xdg_mime_magic_read_to_newline (magic_file, &end_of_file);
269   if (end_of_file)
270     return XDG_MIME_MAGIC_EOF;
271
272   end_ptr = buffer;
273   while (*end_ptr != ']' && *end_ptr != '\000' && *end_ptr != '\n')
274     end_ptr++;
275   if (*end_ptr != ']')
276     {
277       free (buffer);
278       return XDG_MIME_MAGIC_ERROR;
279     }
280   *end_ptr = '\000';
281
282   match->mime_type = strdup (buffer);
283   free (buffer);
284
285   return XDG_MIME_MAGIC_MAGIC;
286 }
287
288 static XdgMimeMagicState
289 _xdg_mime_magic_parse_error (FILE *magic_file)
290 {
291   int c;
292
293   while (1)
294     {
295       c = fgetc (magic_file);
296       if (c == EOF)
297         return XDG_MIME_MAGIC_EOF;
298       if (c == '\n')
299         return XDG_MIME_MAGIC_SECTION;
300     }
301 }
302
303 /* Headers are of the format:
304  * [ indent ] ">" start-offset "=" value
305  * [ "&" mask ] [ "~" word-size ] [ "+" range-length ] "\n"
306  */
307 static XdgMimeMagicState
308 _xdg_mime_magic_parse_magic_line (FILE              *magic_file,
309                                   XdgMimeMagicMatch *match)
310 {
311   XdgMimeMagicMatchlet *matchlet;
312   int c;
313   int end_of_file;
314   int indent = 0;
315   int bytes_read;
316
317   assert (magic_file != NULL);
318
319   /* Sniff the buffer to make sure it's a valid line */
320   c = fgetc (magic_file);
321   if (c == EOF)
322     return XDG_MIME_MAGIC_EOF;
323   else if (c == '[')
324     {
325       ungetc (c, magic_file);
326       return XDG_MIME_MAGIC_SECTION;
327     }
328   else if (c == '\n')
329     return XDG_MIME_MAGIC_MAGIC;
330
331   /* At this point, it must be a digit or a '>' */
332   end_of_file = FALSE;
333   if (isdigit (c))
334     {
335       ungetc (c, magic_file);
336       indent = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
337       if (end_of_file)
338         return XDG_MIME_MAGIC_EOF;
339       if (indent == -1)
340         return XDG_MIME_MAGIC_ERROR;
341       c = fgetc (magic_file);
342       if (c == EOF)
343         return XDG_MIME_MAGIC_EOF;
344     }
345
346   if (c != '>')
347     return XDG_MIME_MAGIC_ERROR;
348
349   matchlet = _xdg_mime_magic_matchlet_new ();
350   matchlet->indent = indent;
351   matchlet->offset = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
352   if (end_of_file)
353     {
354       _xdg_mime_magic_matchlet_free (matchlet);
355       return XDG_MIME_MAGIC_EOF;
356     }
357   if (matchlet->offset == -1)
358     {
359       _xdg_mime_magic_matchlet_free (matchlet);
360       return XDG_MIME_MAGIC_ERROR;
361     }
362   c = fgetc (magic_file);
363   if (c == EOF)
364     {
365       _xdg_mime_magic_matchlet_free (matchlet);
366       return XDG_MIME_MAGIC_EOF;
367     }
368   else if (c != '=')
369     {
370       _xdg_mime_magic_matchlet_free (matchlet);
371       return XDG_MIME_MAGIC_ERROR;
372     }
373
374   /* Next two bytes determine how long the value is */
375   matchlet->value_length = 0;
376   c = fgetc (magic_file);
377   if (c == EOF)
378     {
379       _xdg_mime_magic_matchlet_free (matchlet);
380       return XDG_MIME_MAGIC_EOF;
381     }
382   matchlet->value_length = c & 0xFF;
383   matchlet->value_length = matchlet->value_length << 8;
384
385   c = fgetc (magic_file);
386   if (c == EOF)
387     {
388       _xdg_mime_magic_matchlet_free (matchlet);
389       return XDG_MIME_MAGIC_EOF;
390     }
391   matchlet->value_length = matchlet->value_length + (c & 0xFF);
392
393   matchlet->value = malloc (matchlet->value_length);
394
395   /* OOM */
396   if (matchlet->value == NULL)
397     {
398       _xdg_mime_magic_matchlet_free (matchlet);
399       return XDG_MIME_MAGIC_ERROR;
400     }
401   bytes_read = fread (matchlet->value, 1, matchlet->value_length, magic_file);
402   if (bytes_read != matchlet->value_length)
403     {
404       _xdg_mime_magic_matchlet_free (matchlet);
405       if (feof (magic_file))
406         return XDG_MIME_MAGIC_EOF;
407       else
408         return XDG_MIME_MAGIC_ERROR;
409     }
410
411   c = fgetc (magic_file);
412   if (c == '&')
413     {
414       matchlet->mask = malloc (matchlet->value_length);
415       /* OOM */
416       if (matchlet->mask == NULL)
417         {
418           _xdg_mime_magic_matchlet_free (matchlet);
419           return XDG_MIME_MAGIC_ERROR;
420         }
421       bytes_read = fread (matchlet->mask, 1, matchlet->value_length, magic_file);
422       if (bytes_read != matchlet->value_length)
423         {
424           _xdg_mime_magic_matchlet_free (matchlet);
425           if (feof (magic_file))
426             return XDG_MIME_MAGIC_EOF;
427           else
428             return XDG_MIME_MAGIC_ERROR;
429         }
430       c = fgetc (magic_file);
431     }
432
433   if (c == '~')
434     {
435       matchlet->word_size = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
436       if (end_of_file)
437         {
438           _xdg_mime_magic_matchlet_free (matchlet);
439           return XDG_MIME_MAGIC_EOF;
440         }
441       if (matchlet->word_size != 0 &&
442           matchlet->word_size != 1 &&
443           matchlet->word_size != 2 &&
444           matchlet->word_size != 4)
445         {
446           _xdg_mime_magic_matchlet_free (matchlet);
447           return XDG_MIME_MAGIC_ERROR;
448         }
449       c = fgetc (magic_file);
450     }
451
452   if (c == '+')
453     {
454       matchlet->range_length = _xdg_mime_magic_read_a_number (magic_file, &end_of_file);
455       if (end_of_file)
456         {
457           _xdg_mime_magic_matchlet_free (matchlet);
458           return XDG_MIME_MAGIC_EOF;
459         }
460       if (matchlet->range_length == -1)
461         {
462           _xdg_mime_magic_matchlet_free (matchlet);
463           return XDG_MIME_MAGIC_ERROR;
464         }
465       c = fgetc (magic_file);
466     }
467
468
469   if (c == '\n')
470     {
471       /* We clean up the matchlet, byte swapping if needed */
472       if (matchlet->word_size > 1)
473         {
474           int i;
475           if (matchlet->value_length % matchlet->word_size != 0)
476             {
477               _xdg_mime_magic_matchlet_free (matchlet);
478               return XDG_MIME_MAGIC_ERROR;
479             }
480           /* FIXME: need to get this defined in a <config.h> style file */
481 #if LITTLE_ENDIAN
482           for (i = 0; i < matchlet->value_length; i = i + matchlet->word_size)
483             {
484               if (matchlet->word_size == 2)
485                 *((xdg_uint16_t *) matchlet->value + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->value + i)));
486               else if (matchlet->word_size == 4)
487                 *((xdg_uint32_t *) matchlet->value + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->value + i)));
488               if (matchlet->mask)
489                 {
490                   if (matchlet->word_size == 2)
491                     *((xdg_uint16_t *) matchlet->mask + i) = SWAP_BE16_TO_LE16 (*((xdg_uint16_t *) (matchlet->mask + i)));
492                   else if (matchlet->word_size == 4)
493                     *((xdg_uint32_t *) matchlet->mask + i) = SWAP_BE32_TO_LE32 (*((xdg_uint32_t *) (matchlet->mask + i)));
494
495                 }
496             }
497 #endif
498         }
499
500       matchlet->next = match->matchlet;
501       match->matchlet = matchlet;
502
503
504       return XDG_MIME_MAGIC_MAGIC;
505     }
506
507   _xdg_mime_magic_matchlet_free (matchlet);
508   if (c == EOF)
509     return XDG_MIME_MAGIC_EOF;
510
511   return XDG_MIME_MAGIC_ERROR;
512 }
513
514 static int
515 _xdg_mime_magic_matchlet_compare_to_data (XdgMimeMagicMatchlet *matchlet,
516                                           const void           *data,
517                                           size_t                len)
518 {
519   int i, j;
520
521   for (i = matchlet->offset; i <= matchlet->offset + matchlet->range_length; i++)
522     {
523       int valid_matchlet = TRUE;
524
525       if (i + matchlet->value_length > len)
526         return FALSE;
527
528       if (matchlet->mask)
529         {
530           for (j = 0; j < matchlet->value_length; j++)
531             {
532               if ((matchlet->value[j] & matchlet->mask[j]) !=
533                   ((((unsigned char *) data)[j + i]) & matchlet->mask[j]))
534                 {
535                   valid_matchlet = FALSE;
536                   break;
537                 }
538             }
539         }
540       else
541         {
542           for (j = 0; j <  matchlet->value_length; j++)
543             {
544               if (matchlet->value[j] != ((unsigned char *) data)[j + i])
545                 {
546                   valid_matchlet = FALSE;
547                   break;
548                 }
549             }
550         }
551       if (valid_matchlet)
552         return TRUE;
553     }
554   return FALSE;
555 }
556
557 static int
558 _xdg_mime_magic_matchlet_compare_level (XdgMimeMagicMatchlet *matchlet,
559                                         const void           *data,
560                                         size_t                len,
561                                         int                   indent)
562 {
563   while (matchlet != NULL && matchlet->indent == indent)
564     {
565       if (_xdg_mime_magic_matchlet_compare_to_data (matchlet, data, len))
566         {
567           if (matchlet->next == NULL ||
568               matchlet->next->indent <= indent)
569             return TRUE;
570
571           if (_xdg_mime_magic_matchlet_compare_level (matchlet->next,
572                                                       data,
573                                                       len,
574                                                       indent + 1))
575             return TRUE;
576         }
577
578       do
579         {
580           matchlet = matchlet->next;
581         }
582       while (matchlet && matchlet->indent > indent);
583     }
584
585   return FALSE;
586 }
587
588 static int
589 _xdg_mime_magic_match_compare_to_data (XdgMimeMagicMatch *match,
590                                        const void        *data,
591                                        size_t             len)
592 {
593   return _xdg_mime_magic_matchlet_compare_level (match->matchlet, data, len, 0);
594 }
595
596 static void
597 _xdg_mime_magic_insert_match (XdgMimeMagic      *mime_magic,
598                               XdgMimeMagicMatch *match)
599 {
600   XdgMimeMagicMatch *list;
601
602   if (mime_magic->match_list == NULL)
603     {
604       mime_magic->match_list = match;
605       return;
606     }
607
608   if (match->priority > mime_magic->match_list->priority)
609     {
610       match->next = mime_magic->match_list;
611       mime_magic->match_list = match;
612       return;
613     }
614
615   list = mime_magic->match_list;
616   while (list->next != NULL)
617     {
618       if (list->next->priority < match->priority)
619         {
620           match->next = list->next;
621           list->next = match;
622           return;
623         }
624       list = list->next;
625     }
626   list->next = match;
627   match->next = NULL;
628 }
629
630 XdgMimeMagic *
631 _xdg_mime_magic_new (void)
632 {
633   return calloc (1, sizeof (XdgMimeMagic));
634 }
635
636 void
637 _xdg_mime_magic_free (XdgMimeMagic *mime_magic)
638 {
639   if (mime_magic)
640     free (mime_magic);
641 }
642
643 int
644 _xdg_mime_magic_get_buffer_extents (XdgMimeMagic *mime_magic)
645 {
646   return mime_magic->max_extent;
647 }
648
649 const char *
650 _xdg_mime_magic_lookup_data (XdgMimeMagic *mime_magic,
651                              const void   *data,
652                              size_t        len)
653 {
654   XdgMimeMagicMatch *match;
655
656   for (match = mime_magic->match_list; match; match = match->next)
657     {
658       if (_xdg_mime_magic_match_compare_to_data (match, data, len))
659         {
660           return match->mime_type;
661         }
662     }
663
664   return NULL;
665 }
666
667 static void
668 _xdg_mime_update_mime_magic_extents (XdgMimeMagic *mime_magic)
669 {
670   XdgMimeMagicMatch *match;
671   int max_extent = 0;
672
673   for (match = mime_magic->match_list; match; match = match->next)
674     {
675       XdgMimeMagicMatchlet *matchlet;
676
677       for (matchlet = match->matchlet; matchlet; matchlet = matchlet->next)
678         {
679           int extent;
680
681           extent = matchlet->value_length + matchlet->offset + matchlet->range_length;
682           if (max_extent < extent)
683             max_extent = extent;
684         }
685     }
686
687   mime_magic->max_extent = max_extent;
688 }
689
690 static void
691 _xdg_mime_magic_read_magic_file (XdgMimeMagic *mime_magic,
692                                  FILE         *magic_file)
693 {
694   XdgMimeMagicState state;
695   XdgMimeMagicMatch *match = NULL; /* Quiet compiler */
696
697   state = XDG_MIME_MAGIC_SECTION;
698
699   while (state != XDG_MIME_MAGIC_EOF)
700     {
701       switch (state)
702         {
703         case XDG_MIME_MAGIC_SECTION:
704           match = _xdg_mime_magic_match_new ();
705           state = _xdg_mime_magic_parse_header (magic_file, match);
706           if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR)
707             _xdg_mime_magic_match_free (match);
708           break;
709         case XDG_MIME_MAGIC_MAGIC:
710           state = _xdg_mime_magic_parse_magic_line (magic_file, match);
711           if (state == XDG_MIME_MAGIC_SECTION)
712             _xdg_mime_magic_insert_match (mime_magic, match);
713           else if (state == XDG_MIME_MAGIC_EOF || state == XDG_MIME_MAGIC_ERROR)
714             _xdg_mime_magic_match_free (match);
715           break;
716         case XDG_MIME_MAGIC_ERROR:
717           state = _xdg_mime_magic_parse_error (magic_file);
718           break;
719         case XDG_MIME_MAGIC_EOF:
720         default:
721           /* Make the compiler happy */
722           assert (0);
723         }
724     }
725   _xdg_mime_update_mime_magic_extents (mime_magic);
726 }
727
728 void
729 _xdg_mime_magic_read_from_file (XdgMimeMagic *mime_magic,
730                                 const char   *file_name)
731 {
732   FILE *magic_file;
733   char header[12];
734
735   magic_file = fopen (file_name, "r");
736
737   if (magic_file == NULL)
738     return;
739
740   fread (header, 1, 12, magic_file);
741
742   if (memcmp ("MIME-Magic\0\n", header, 12) == 0)
743     _xdg_mime_magic_read_magic_file (mime_magic, magic_file);
744   fclose (magic_file);
745 }