xdgmimeglob.c 15 KB

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