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