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