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