]>
Commit | Line | Data |
---|---|---|
cf31d981 SI |
1 | /* -*- mode: C; c-file-style: "gnu" -*- */ |
2 | /* xdgmimeglob.c: Private file. Datastructure for storing the globs. | |
3 | * | |
4 | * More info can be found at http://www.freedesktop.org/standards/ | |
5 | * | |
6 | * Copyright (C) 2003 Red Hat, Inc. | |
7 | * Copyright (C) 2003 Jonathan Blandford <jrb@alum.mit.edu> | |
8 | * | |
73d5a30a | 9 | * SPDX-License-Identifier: LGPL-2.1-or-later or AFL-2.0 |
cf31d981 SI |
10 | */ |
11 | ||
12 | #ifdef HAVE_CONFIG_H | |
13 | #include <config.h> | |
14 | #endif | |
15 | ||
16 | #include "xdgmimeglob.h" | |
17 | #include "xdgmimeint.h" | |
18 | #include <stdlib.h> | |
19 | #include <stdio.h> | |
20 | #include <assert.h> | |
21 | #include <string.h> | |
22 | #include <fnmatch.h> | |
23 | ||
24 | #ifndef FALSE | |
25 | #define FALSE (0) | |
26 | #endif | |
27 | ||
28 | #ifndef TRUE | |
29 | #define TRUE (!FALSE) | |
30 | #endif | |
31 | ||
32 | typedef struct XdgGlobHashNode XdgGlobHashNode; | |
33 | typedef struct XdgGlobList XdgGlobList; | |
34 | ||
35 | struct XdgGlobHashNode | |
36 | { | |
37 | xdg_unichar_t character; | |
38 | const char *mime_type; | |
39 | int weight; | |
40 | int case_sensitive; | |
41 | XdgGlobHashNode *next; | |
42 | XdgGlobHashNode *child; | |
43 | }; | |
44 | struct XdgGlobList | |
45 | { | |
46 | const char *data; | |
47 | const char *mime_type; | |
48 | int weight; | |
49 | int case_sensitive; | |
50 | XdgGlobList *next; | |
51 | }; | |
52 | ||
53 | struct XdgGlobHash | |
54 | { | |
55 | XdgGlobList *literal_list; | |
56 | XdgGlobHashNode *simple_node; | |
57 | XdgGlobList *full_list; | |
58 | }; | |
59 | ||
60 | ||
61 | /* XdgGlobList | |
62 | */ | |
63 | static XdgGlobList * | |
64 | _xdg_glob_list_new (void) | |
65 | { | |
66 | XdgGlobList *new_element; | |
67 | ||
68 | new_element = calloc (1, sizeof (XdgGlobList)); | |
69 | ||
70 | return new_element; | |
71 | } | |
72 | ||
73 | /* Frees glob_list and all of its children */ | |
74 | static void | |
75 | _xdg_glob_list_free (XdgGlobList *glob_list) | |
76 | { | |
77 | XdgGlobList *ptr, *next; | |
78 | ||
79 | ptr = glob_list; | |
80 | ||
81 | while (ptr != NULL) | |
82 | { | |
83 | next = ptr->next; | |
84 | ||
85 | if (ptr->data) | |
86 | free ((void *) ptr->data); | |
87 | if (ptr->mime_type) | |
88 | free ((void *) ptr->mime_type); | |
89 | free (ptr); | |
90 | ||
91 | ptr = next; | |
92 | } | |
93 | } | |
94 | ||
95 | static XdgGlobList * | |
96 | _xdg_glob_list_append (XdgGlobList *glob_list, | |
97 | void *data, | |
98 | const char *mime_type, | |
99 | int weight, | |
100 | int case_sensitive) | |
101 | { | |
102 | XdgGlobList *new_element; | |
103 | XdgGlobList *tmp_element; | |
104 | ||
105 | tmp_element = glob_list; | |
106 | while (tmp_element != NULL) | |
107 | { | |
108 | if (strcmp (tmp_element->data, data) == 0 && | |
109 | strcmp (tmp_element->mime_type, mime_type) == 0) | |
110 | return glob_list; | |
111 | ||
112 | tmp_element = tmp_element->next; | |
113 | } | |
114 | ||
115 | new_element = _xdg_glob_list_new (); | |
116 | new_element->data = data; | |
117 | new_element->mime_type = mime_type; | |
118 | new_element->weight = weight; | |
119 | new_element->case_sensitive = case_sensitive; | |
120 | if (glob_list == NULL) | |
121 | return new_element; | |
122 | ||
123 | tmp_element = glob_list; | |
124 | while (tmp_element->next != NULL) | |
125 | tmp_element = tmp_element->next; | |
126 | ||
127 | tmp_element->next = new_element; | |
128 | ||
129 | return glob_list; | |
130 | } | |
131 | ||
132 | /* XdgGlobHashNode | |
133 | */ | |
134 | ||
135 | static XdgGlobHashNode * | |
136 | _xdg_glob_hash_node_new (void) | |
137 | { | |
138 | XdgGlobHashNode *glob_hash_node; | |
139 | ||
140 | glob_hash_node = calloc (1, sizeof (XdgGlobHashNode)); | |
141 | ||
142 | return glob_hash_node; | |
143 | } | |
144 | ||
145 | static void | |
146 | _xdg_glob_hash_node_dump (XdgGlobHashNode *glob_hash_node, | |
147 | int depth) | |
148 | { | |
149 | int i; | |
150 | for (i = 0; i < depth; i++) | |
151 | printf (" "); | |
152 | ||
153 | printf ("%c", (char)glob_hash_node->character); | |
154 | if (glob_hash_node->mime_type) | |
155 | printf (" - %s %d\n", glob_hash_node->mime_type, glob_hash_node->weight); | |
156 | else | |
157 | printf ("\n"); | |
158 | if (glob_hash_node->child) | |
159 | _xdg_glob_hash_node_dump (glob_hash_node->child, depth + 1); | |
160 | if (glob_hash_node->next) | |
161 | _xdg_glob_hash_node_dump (glob_hash_node->next, depth); | |
162 | } | |
163 | ||
164 | static XdgGlobHashNode * | |
165 | _xdg_glob_hash_insert_ucs4 (XdgGlobHashNode *glob_hash_node, | |
166 | xdg_unichar_t *text, | |
167 | const char *mime_type, | |
168 | int weight, | |
169 | int case_sensitive) | |
170 | { | |
171 | XdgGlobHashNode *node; | |
172 | xdg_unichar_t character; | |
173 | ||
174 | character = text[0]; | |
175 | ||
176 | if ((glob_hash_node == NULL) || | |
177 | (character < glob_hash_node->character)) | |
178 | { | |
179 | node = _xdg_glob_hash_node_new (); | |
180 | node->character = character; | |
181 | node->next = glob_hash_node; | |
182 | glob_hash_node = node; | |
183 | } | |
184 | else if (character == glob_hash_node->character) | |
185 | { | |
186 | node = glob_hash_node; | |
187 | } | |
188 | else | |
189 | { | |
190 | XdgGlobHashNode *prev_node; | |
191 | int found_node = FALSE; | |
192 | ||
193 | /* Look for the first character of text in glob_hash_node, and insert it if we | |
194 | * have to.*/ | |
195 | prev_node = glob_hash_node; | |
196 | node = prev_node->next; | |
197 | ||
198 | while (node != NULL) | |
199 | { | |
200 | if (character < node->character) | |
201 | { | |
202 | node = _xdg_glob_hash_node_new (); | |
203 | node->character = character; | |
204 | node->next = prev_node->next; | |
205 | prev_node->next = node; | |
206 | ||
207 | found_node = TRUE; | |
208 | break; | |
209 | } | |
210 | else if (character == node->character) | |
211 | { | |
212 | found_node = TRUE; | |
213 | break; | |
214 | } | |
215 | prev_node = node; | |
216 | node = node->next; | |
217 | } | |
218 | ||
219 | if (! found_node) | |
220 | { | |
221 | node = _xdg_glob_hash_node_new (); | |
222 | node->character = character; | |
223 | node->next = prev_node->next; | |
224 | prev_node->next = node; | |
225 | } | |
226 | } | |
227 | ||
228 | text++; | |
229 | if (*text == 0) | |
230 | { | |
231 | if (node->mime_type) | |
232 | { | |
233 | if (strcmp (node->mime_type, mime_type) != 0) | |
234 | { | |
235 | XdgGlobHashNode *child; | |
236 | int found_node = FALSE; | |
237 | ||
238 | child = node->child; | |
239 | while (child && child->character == 0) | |
240 | { | |
241 | if (strcmp (child->mime_type, mime_type) == 0) | |
242 | { | |
243 | found_node = TRUE; | |
244 | break; | |
245 | } | |
246 | child = child->next; | |
247 | } | |
248 | ||
249 | if (!found_node) | |
250 | { | |
251 | child = _xdg_glob_hash_node_new (); | |
252 | child->character = 0; | |
253 | child->mime_type = strdup (mime_type); | |
254 | child->weight = weight; | |
255 | child->case_sensitive = case_sensitive; | |
256 | child->child = NULL; | |
257 | child->next = node->child; | |
258 | node->child = child; | |
259 | } | |
260 | } | |
261 | } | |
262 | else | |
263 | { | |
264 | node->mime_type = strdup (mime_type); | |
265 | node->weight = weight; | |
266 | node->case_sensitive = case_sensitive; | |
267 | } | |
268 | } | |
269 | else | |
270 | { | |
271 | node->child = _xdg_glob_hash_insert_ucs4 (node->child, text, mime_type, weight, case_sensitive); | |
272 | } | |
273 | return glob_hash_node; | |
274 | } | |
275 | ||
276 | /* glob must be valid UTF-8 */ | |
277 | static XdgGlobHashNode * | |
278 | _xdg_glob_hash_insert_text (XdgGlobHashNode *glob_hash_node, | |
279 | const char *text, | |
280 | const char *mime_type, | |
281 | int weight, | |
282 | int case_sensitive) | |
283 | { | |
284 | XdgGlobHashNode *node; | |
285 | xdg_unichar_t *unitext; | |
286 | int len; | |
287 | ||
288 | unitext = _xdg_convert_to_ucs4 (text, &len); | |
289 | _xdg_reverse_ucs4 (unitext, len); | |
290 | node = _xdg_glob_hash_insert_ucs4 (glob_hash_node, unitext, mime_type, weight, case_sensitive); | |
291 | free (unitext); | |
292 | return node; | |
293 | } | |
294 | ||
295 | typedef struct { | |
296 | const char *mime; | |
297 | int weight; | |
298 | } MimeWeight; | |
299 | ||
300 | static int | |
301 | _xdg_glob_hash_node_lookup_file_name (XdgGlobHashNode *glob_hash_node, | |
302 | const char *file_name, | |
303 | int len, | |
304 | int case_sensitive_check, | |
305 | MimeWeight mime_types[], | |
306 | int n_mime_types) | |
307 | { | |
308 | int n; | |
309 | XdgGlobHashNode *node; | |
310 | xdg_unichar_t character; | |
311 | ||
312 | if (glob_hash_node == NULL) | |
313 | return 0; | |
314 | ||
315 | character = file_name[len - 1]; | |
316 | ||
317 | for (node = glob_hash_node; node && character >= node->character; node = node->next) | |
318 | { | |
319 | if (character == node->character) | |
320 | { | |
321 | len--; | |
322 | n = 0; | |
323 | if (len > 0) | |
324 | { | |
325 | n = _xdg_glob_hash_node_lookup_file_name (node->child, | |
326 | file_name, | |
327 | len, | |
328 | case_sensitive_check, | |
329 | mime_types, | |
330 | n_mime_types); | |
331 | } | |
332 | if (n == 0) | |
333 | { | |
334 | if (node->mime_type && | |
335 | (case_sensitive_check || | |
336 | !node->case_sensitive)) | |
337 | { | |
338 | mime_types[n].mime = node->mime_type; | |
339 | mime_types[n].weight = node->weight; | |
340 | n++; | |
341 | } | |
342 | node = node->child; | |
343 | while (n < n_mime_types && node && node->character == 0) | |
344 | { | |
345 | if (node->mime_type && | |
346 | (case_sensitive_check || | |
347 | !node->case_sensitive)) | |
348 | { | |
349 | mime_types[n].mime = node->mime_type; | |
350 | mime_types[n].weight = node->weight; | |
351 | n++; | |
352 | } | |
353 | node = node->next; | |
354 | } | |
355 | } | |
356 | return n; | |
357 | } | |
358 | } | |
359 | ||
360 | return 0; | |
361 | } | |
362 | ||
363 | static int compare_mime_weight (const void *a, const void *b) | |
364 | { | |
365 | const MimeWeight *aa = (const MimeWeight *)a; | |
366 | const MimeWeight *bb = (const MimeWeight *)b; | |
367 | ||
368 | return bb->weight - aa->weight; | |
369 | } | |
370 | ||
371 | #define ISUPPER(c) ((c) >= 'A' && (c) <= 'Z') | |
372 | static char * | |
373 | ascii_tolower (const char *str) | |
374 | { | |
375 | char *p, *lower; | |
376 | ||
377 | lower = strdup (str); | |
378 | p = lower; | |
379 | while (*p != 0) | |
380 | { | |
381 | char c = *p; | |
382 | *p++ = ISUPPER (c) ? c - 'A' + 'a' : c; | |
383 | } | |
384 | return lower; | |
385 | } | |
386 | ||
387 | int | |
388 | _xdg_glob_hash_lookup_file_name (XdgGlobHash *glob_hash, | |
389 | const char *file_name, | |
390 | const char *mime_types[], | |
391 | int n_mime_types) | |
392 | { | |
393 | XdgGlobList *list; | |
394 | int i, n; | |
395 | MimeWeight mimes[10]; | |
396 | int n_mimes = 10; | |
397 | int len; | |
398 | char *lower_case; | |
399 | ||
400 | /* First, check the literals */ | |
401 | ||
402 | assert (file_name != NULL && n_mime_types > 0); | |
403 | ||
404 | n = 0; | |
405 | ||
406 | lower_case = ascii_tolower (file_name); | |
407 | ||
408 | for (list = glob_hash->literal_list; list; list = list->next) | |
409 | { | |
410 | if (strcmp ((const char *)list->data, file_name) == 0) | |
411 | { | |
412 | mime_types[0] = list->mime_type; | |
413 | free (lower_case); | |
414 | return 1; | |
415 | } | |
416 | } | |
417 | ||
418 | for (list = glob_hash->literal_list; list; list = list->next) | |
419 | { | |
420 | if (!list->case_sensitive && | |
421 | strcmp ((const char *)list->data, lower_case) == 0) | |
422 | { | |
423 | mime_types[0] = list->mime_type; | |
424 | free (lower_case); | |
425 | return 1; | |
426 | } | |
427 | } | |
428 | ||
429 | ||
430 | len = strlen (file_name); | |
431 | n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, lower_case, len, FALSE, | |
432 | mimes, n_mimes); | |
433 | if (n == 0) | |
434 | n = _xdg_glob_hash_node_lookup_file_name (glob_hash->simple_node, file_name, len, TRUE, | |
435 | mimes, n_mimes); | |
436 | ||
437 | if (n == 0) | |
438 | { | |
439 | for (list = glob_hash->full_list; list && n < n_mime_types; list = list->next) | |
440 | { | |
441 | if (fnmatch ((const char *)list->data, file_name, 0) == 0) | |
442 | { | |
443 | mimes[n].mime = list->mime_type; | |
444 | mimes[n].weight = list->weight; | |
445 | n++; | |
446 | } | |
447 | } | |
448 | } | |
449 | free (lower_case); | |
450 | ||
451 | qsort (mimes, n, sizeof (MimeWeight), compare_mime_weight); | |
452 | ||
453 | if (n_mime_types < n) | |
454 | n = n_mime_types; | |
455 | ||
456 | for (i = 0; i < n; i++) | |
457 | mime_types[i] = mimes[i].mime; | |
458 | ||
459 | return n; | |
460 | } | |
461 | ||
462 | ||
463 | ||
464 | /* XdgGlobHash | |
465 | */ | |
466 | ||
467 | XdgGlobHash * | |
468 | _xdg_glob_hash_new (void) | |
469 | { | |
470 | XdgGlobHash *glob_hash; | |
471 | ||
472 | glob_hash = calloc (1, sizeof (XdgGlobHash)); | |
473 | ||
474 | return glob_hash; | |
475 | } | |
476 | ||
477 | ||
478 | static void | |
479 | _xdg_glob_hash_free_nodes (XdgGlobHashNode *node) | |
480 | { | |
481 | if (node) | |
482 | { | |
483 | if (node->child) | |
484 | _xdg_glob_hash_free_nodes (node->child); | |
485 | if (node->next) | |
486 | _xdg_glob_hash_free_nodes (node->next); | |
487 | if (node->mime_type) | |
488 | free ((void *) node->mime_type); | |
489 | free (node); | |
490 | } | |
491 | } | |
492 | ||
493 | void | |
494 | _xdg_glob_hash_free (XdgGlobHash *glob_hash) | |
495 | { | |
496 | _xdg_glob_list_free (glob_hash->literal_list); | |
497 | _xdg_glob_list_free (glob_hash->full_list); | |
498 | _xdg_glob_hash_free_nodes (glob_hash->simple_node); | |
499 | free (glob_hash); | |
500 | } | |
501 | ||
502 | XdgGlobType | |
503 | _xdg_glob_determine_type (const char *glob) | |
504 | { | |
505 | const char *ptr; | |
506 | int maybe_in_simple_glob = FALSE; | |
507 | int first_char = TRUE; | |
508 | ||
509 | ptr = glob; | |
510 | ||
511 | while (*ptr != '\0') | |
512 | { | |
513 | if (*ptr == '*' && first_char) | |
514 | maybe_in_simple_glob = TRUE; | |
515 | else if (*ptr == '\\' || *ptr == '[' || *ptr == '?' || *ptr == '*') | |
516 | return XDG_GLOB_FULL; | |
517 | ||
518 | first_char = FALSE; | |
519 | ptr = _xdg_utf8_next_char (ptr); | |
520 | } | |
521 | if (maybe_in_simple_glob) | |
522 | return XDG_GLOB_SIMPLE; | |
523 | else | |
524 | return XDG_GLOB_LITERAL; | |
525 | } | |
526 | ||
527 | /* glob must be valid UTF-8 */ | |
528 | void | |
529 | _xdg_glob_hash_append_glob (XdgGlobHash *glob_hash, | |
530 | const char *glob, | |
531 | const char *mime_type, | |
532 | int weight, | |
533 | int case_sensitive) | |
534 | { | |
535 | XdgGlobType type; | |
536 | ||
537 | assert (glob_hash != NULL); | |
538 | assert (glob != NULL); | |
539 | ||
540 | type = _xdg_glob_determine_type (glob); | |
541 | ||
542 | switch (type) | |
543 | { | |
544 | case XDG_GLOB_LITERAL: | |
545 | glob_hash->literal_list = _xdg_glob_list_append (glob_hash->literal_list, strdup (glob), strdup (mime_type), weight, case_sensitive); | |
546 | break; | |
547 | case XDG_GLOB_SIMPLE: | |
548 | glob_hash->simple_node = _xdg_glob_hash_insert_text (glob_hash->simple_node, glob + 1, mime_type, weight, case_sensitive); | |
549 | break; | |
550 | case XDG_GLOB_FULL: | |
551 | glob_hash->full_list = _xdg_glob_list_append (glob_hash->full_list, strdup (glob), strdup (mime_type), weight, case_sensitive); | |
552 | break; | |
553 | } | |
554 | } | |
555 | ||
556 | void | |
557 | _xdg_glob_hash_dump (XdgGlobHash *glob_hash) | |
558 | { | |
559 | XdgGlobList *list; | |
560 | printf ("LITERAL STRINGS\n"); | |
561 | if (!glob_hash || glob_hash->literal_list == NULL) | |
562 | { | |
563 | printf (" None\n"); | |
564 | } | |
565 | else | |
566 | { | |
567 | for (list = glob_hash->literal_list; list; list = list->next) | |
568 | printf (" %s - %s %d\n", (char *)list->data, list->mime_type, list->weight); | |
569 | } | |
570 | printf ("\nSIMPLE GLOBS\n"); | |
571 | if (!glob_hash || glob_hash->simple_node == NULL) | |
572 | { | |
573 | printf (" None\n"); | |
574 | } | |
575 | else | |
576 | { | |
577 | _xdg_glob_hash_node_dump (glob_hash->simple_node, 4); | |
578 | } | |
579 | ||
580 | printf ("\nFULL GLOBS\n"); | |
581 | if (!glob_hash || glob_hash->full_list == NULL) | |
582 | { | |
583 | printf (" None\n"); | |
584 | } | |
585 | else | |
586 | { | |
587 | for (list = glob_hash->full_list; list; list = list->next) | |
588 | printf (" %s - %s %d\n", (char *)list->data, list->mime_type, list->weight); | |
589 | } | |
590 | } | |
591 | ||
592 | ||
593 | void | |
594 | _xdg_mime_glob_read_from_file (XdgGlobHash *glob_hash, | |
595 | const char *file_name, | |
596 | int version_two) | |
597 | { | |
598 | FILE *glob_file; | |
599 | char line[255]; | |
600 | char *p; | |
601 | ||
602 | glob_file = fopen (file_name, "r"); | |
603 | ||
604 | if (glob_file == NULL) | |
605 | return; | |
606 | ||
607 | /* FIXME: Not UTF-8 safe. Doesn't work if lines are greater than 255 chars. | |
608 | * Blah */ | |
609 | while (fgets (line, 255, glob_file) != NULL) | |
610 | { | |
611 | char *colon; | |
612 | char *mimetype, *glob, *end; | |
613 | int weight; | |
614 | int case_sensitive; | |
615 | ||
616 | if (line[0] == '#' || line[0] == 0) | |
617 | continue; | |
618 | ||
619 | end = line + strlen(line) - 1; | |
620 | if (*end == '\n') | |
621 | *end = 0; | |
622 | ||
623 | p = line; | |
624 | if (version_two) | |
625 | { | |
626 | colon = strchr (p, ':'); | |
627 | if (colon == NULL) | |
628 | continue; | |
629 | *colon = 0; | |
630 | weight = atoi (p); | |
631 | p = colon + 1; | |
632 | } | |
633 | else | |
634 | weight = 50; | |
635 | ||
636 | colon = strchr (p, ':'); | |
637 | if (colon == NULL) | |
638 | continue; | |
639 | *colon = 0; | |
640 | ||
641 | mimetype = p; | |
642 | p = colon + 1; | |
643 | glob = p; | |
644 | case_sensitive = FALSE; | |
645 | ||
646 | colon = strchr (p, ':'); | |
647 | if (version_two && colon != NULL) | |
648 | { | |
649 | char *flag; | |
650 | ||
651 | /* We got flags */ | |
652 | *colon = 0; | |
653 | p = colon + 1; | |
654 | ||
655 | /* Flags end at next colon */ | |
656 | colon = strchr (p, ':'); | |
657 | if (colon != NULL) | |
658 | *colon = 0; | |
659 | ||
660 | flag = strstr (p, "cs"); | |
661 | if (flag != NULL && | |
662 | /* Start or after comma */ | |
663 | (flag == p || | |
664 | flag[-1] == ',') && | |
665 | /* ends with comma or end of string */ | |
666 | (flag[2] == 0 || | |
667 | flag[2] == ',')) | |
668 | case_sensitive = TRUE; | |
669 | } | |
670 | ||
671 | _xdg_glob_hash_append_glob (glob_hash, glob, mimetype, weight, case_sensitive); | |
672 | } | |
673 | ||
674 | fclose (glob_file); | |
675 | } |