Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 : :
3 : : /* GIO - GLib Input, Output and Streaming Library
4 : : *
5 : : * Copyright (C) 2006-2007 Red Hat, Inc.
6 : : *
7 : : * SPDX-License-Identifier: LGPL-2.1-or-later
8 : : *
9 : : * This library is free software; you can redistribute it and/or
10 : : * modify it under the terms of the GNU Lesser General Public
11 : : * License as published by the Free Software Foundation; either
12 : : * version 2.1 of the License, or (at your option) any later version.
13 : : *
14 : : * This library is distributed in the hope that it will be useful,
15 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 : : * Lesser General Public License for more details.
18 : : *
19 : : * You should have received a copy of the GNU Lesser General
20 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
21 : : *
22 : : * Author: Alexander Larsson <alexl@redhat.com>
23 : : */
24 : :
25 : : #include "config.h"
26 : : #include <sys/types.h>
27 : : #include <stdlib.h>
28 : : #include <string.h>
29 : : #include <stdio.h>
30 : : #include "gcontenttypeprivate.h"
31 : : #include "gthemedicon.h"
32 : : #include "gicon.h"
33 : : #include "gfile.h"
34 : : #include "gfileenumerator.h"
35 : : #include "gfileinfo.h"
36 : : #include "glibintl.h"
37 : : #include "glib-private.h"
38 : :
39 : :
40 : : /**
41 : : * GContentType:
42 : : *
43 : : * A content type is a platform specific string that defines the type
44 : : * of a file. On UNIX it is a
45 : : * [MIME type](http://www.wikipedia.org/wiki/Internet_media_type)
46 : : * like `text/plain` or `image/png`.
47 : : * On Win32 it is an extension string like `.doc`, `.txt` or a perceived
48 : : * string like `audio`. Such strings can be looked up in the registry at
49 : : * `HKEY_CLASSES_ROOT`.
50 : : * On macOS it is a [Uniform Type Identifier](https://en.wikipedia.org/wiki/Uniform_Type_Identifier)
51 : : * such as `com.apple.application`.
52 : : **/
53 : :
54 : : #include <dirent.h>
55 : :
56 : : #define XDG_PREFIX _gio_xdg
57 : : #include "xdgmime/xdgmime.h"
58 : :
59 : : static void tree_magic_schedule_reload (void);
60 : :
61 : : /* We lock this mutex whenever we modify global state in this module.
62 : : * Taking and releasing this lock should always be associated with a pair of
63 : : * g_begin_ignore_leaks()/g_end_ignore_leaks() calls, as any call into xdgmime
64 : : * could trigger xdg_mime_init(), which makes a number of one-time allocations
65 : : * which GLib can never free as it doesn’t know when is suitable to call
66 : : * xdg_mime_shutdown(). */
67 : : G_LOCK_DEFINE_STATIC (gio_xdgmime);
68 : :
69 : : gsize
70 : 10 : _g_unix_content_type_get_sniff_len (void)
71 : : {
72 : : gsize size;
73 : :
74 : 10 : G_LOCK (gio_xdgmime);
75 : 10 : g_begin_ignore_leaks ();
76 : 10 : size = xdg_mime_get_max_buffer_extents ();
77 : 10 : g_end_ignore_leaks ();
78 : 10 : G_UNLOCK (gio_xdgmime);
79 : :
80 : 10 : return size;
81 : : }
82 : :
83 : : gchar *
84 : 10091 : _g_unix_content_type_unalias (const gchar *type)
85 : : {
86 : : gchar *res;
87 : :
88 : 10091 : G_LOCK (gio_xdgmime);
89 : 10091 : g_begin_ignore_leaks ();
90 : 10091 : res = g_strdup (xdg_mime_unalias_mime_type (type));
91 : 10091 : g_end_ignore_leaks ();
92 : 10091 : G_UNLOCK (gio_xdgmime);
93 : :
94 : 10091 : return res;
95 : : }
96 : :
97 : : gchar **
98 : 69 : _g_unix_content_type_get_parents (const gchar *type)
99 : : {
100 : : const gchar *umime;
101 : : gchar **parents;
102 : : GPtrArray *array;
103 : : int i;
104 : :
105 : 69 : array = g_ptr_array_new ();
106 : :
107 : 69 : G_LOCK (gio_xdgmime);
108 : 69 : g_begin_ignore_leaks ();
109 : :
110 : 69 : umime = xdg_mime_unalias_mime_type (type);
111 : :
112 : 69 : g_ptr_array_add (array, g_strdup (umime));
113 : :
114 : 69 : parents = xdg_mime_list_mime_parents (umime);
115 [ + + + + ]: 75 : for (i = 0; parents && parents[i] != NULL; i++)
116 : 12 : g_ptr_array_add (array, g_strdup (parents[i]));
117 : :
118 : 69 : free (parents);
119 : :
120 : 69 : g_end_ignore_leaks ();
121 : 69 : G_UNLOCK (gio_xdgmime);
122 : :
123 : 69 : g_ptr_array_add (array, NULL);
124 : :
125 : 69 : return (gchar **)g_ptr_array_free (array, FALSE);
126 : : }
127 : :
128 : : G_LOCK_DEFINE_STATIC (global_mime_dirs);
129 : : static gchar **global_mime_dirs = NULL;
130 : :
131 : : static void
132 : 2 : _g_content_type_set_mime_dirs_locked (const char * const *dirs)
133 : : {
134 : 2 : g_clear_pointer (&global_mime_dirs, g_strfreev);
135 : :
136 [ - + ]: 2 : if (dirs != NULL)
137 : : {
138 : 0 : global_mime_dirs = g_strdupv ((gchar **) dirs);
139 : : }
140 : : else
141 : : {
142 : 2 : GPtrArray *mime_dirs = g_ptr_array_new_with_free_func (g_free);
143 : 2 : const gchar * const *system_dirs = g_get_system_data_dirs ();
144 : :
145 : 2 : g_ptr_array_add (mime_dirs, g_build_filename (g_get_user_data_dir (), "mime", NULL));
146 [ + + ]: 6 : for (; *system_dirs != NULL; system_dirs++)
147 : 4 : g_ptr_array_add (mime_dirs, g_build_filename (*system_dirs, "mime", NULL));
148 : 2 : g_ptr_array_add (mime_dirs, NULL); /* NULL terminator */
149 : :
150 : 2 : global_mime_dirs = (gchar **) g_ptr_array_free (mime_dirs, FALSE);
151 : : }
152 : :
153 : 2 : xdg_mime_set_dirs ((const gchar * const *) global_mime_dirs);
154 : 2 : tree_magic_schedule_reload ();
155 : 2 : }
156 : :
157 : : /**
158 : : * g_content_type_set_mime_dirs:
159 : : * @dirs: (array zero-terminated=1) (nullable): %NULL-terminated list of
160 : : * directories to load MIME data from, including any `mime/` subdirectory,
161 : : * and with the first directory to try listed first
162 : : *
163 : : * Set the list of directories used by GIO to load the MIME database.
164 : : * If @dirs is %NULL, the directories used are the default:
165 : : *
166 : : * - the `mime` subdirectory of the directory in `$XDG_DATA_HOME`
167 : : * - the `mime` subdirectory of every directory in `$XDG_DATA_DIRS`
168 : : *
169 : : * This function is intended to be used when writing tests that depend on
170 : : * information stored in the MIME database, in order to control the data.
171 : : *
172 : : * Typically, in case your tests use %G_TEST_OPTION_ISOLATE_DIRS, but they
173 : : * depend on the system’s MIME database, you should call this function
174 : : * with @dirs set to %NULL before calling g_test_init(), for instance:
175 : : *
176 : : * |[<!-- language="C" -->
177 : : * // Load MIME data from the system
178 : : * g_content_type_set_mime_dirs (NULL);
179 : : * // Isolate the environment
180 : : * g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
181 : : *
182 : : * …
183 : : *
184 : : * return g_test_run ();
185 : : * ]|
186 : : *
187 : : * Since: 2.60
188 : : */
189 : : /*< private >*/
190 : : void
191 : 1 : g_content_type_set_mime_dirs (const gchar * const *dirs)
192 : : {
193 : 1 : G_LOCK (global_mime_dirs);
194 : 1 : _g_content_type_set_mime_dirs_locked (dirs);
195 : 1 : G_UNLOCK (global_mime_dirs);
196 : 1 : }
197 : :
198 : : /**
199 : : * g_content_type_get_mime_dirs:
200 : : *
201 : : * Get the list of directories which MIME data is loaded from. See
202 : : * g_content_type_set_mime_dirs() for details.
203 : : *
204 : : * Returns: (transfer none) (array zero-terminated=1): %NULL-terminated list of
205 : : * directories to load MIME data from, including any `mime/` subdirectory,
206 : : * and with the first directory to try listed first
207 : : * Since: 2.60
208 : : */
209 : : /*< private >*/
210 : : const gchar * const *
211 : 3 : g_content_type_get_mime_dirs (void)
212 : : {
213 : : const gchar * const *mime_dirs;
214 : :
215 : 3 : G_LOCK (global_mime_dirs);
216 : :
217 [ + + ]: 3 : if (global_mime_dirs == NULL)
218 : 1 : _g_content_type_set_mime_dirs_locked (NULL);
219 : :
220 : 3 : mime_dirs = (const gchar * const *) global_mime_dirs;
221 : :
222 : 3 : G_UNLOCK (global_mime_dirs);
223 : :
224 : 3 : g_assert (mime_dirs != NULL);
225 : 3 : return mime_dirs;
226 : : }
227 : :
228 : : /**
229 : : * g_content_type_equals:
230 : : * @type1: a content type string
231 : : * @type2: a content type string
232 : : *
233 : : * Compares two content types for equality.
234 : : *
235 : : * Returns: %TRUE if the two strings are identical or equivalent,
236 : : * %FALSE otherwise.
237 : : */
238 : : gboolean
239 : 370 : g_content_type_equals (const gchar *type1,
240 : : const gchar *type2)
241 : : {
242 : : gboolean res;
243 : :
244 : 370 : g_return_val_if_fail (type1 != NULL, FALSE);
245 : 370 : g_return_val_if_fail (type2 != NULL, FALSE);
246 : :
247 : 370 : G_LOCK (gio_xdgmime);
248 : 370 : g_begin_ignore_leaks ();
249 : 370 : res = xdg_mime_mime_type_equal (type1, type2);
250 : 370 : g_end_ignore_leaks ();
251 : 370 : G_UNLOCK (gio_xdgmime);
252 : :
253 : 370 : return res;
254 : : }
255 : :
256 : : /**
257 : : * g_content_type_is_a:
258 : : * @type: a content type string
259 : : * @supertype: a content type string
260 : : *
261 : : * Determines if @type is a subset of @supertype.
262 : : *
263 : : * Returns: %TRUE if @type is a kind of @supertype,
264 : : * %FALSE otherwise.
265 : : */
266 : : gboolean
267 : 10 : g_content_type_is_a (const gchar *type,
268 : : const gchar *supertype)
269 : : {
270 : : gboolean res;
271 : :
272 : 10 : g_return_val_if_fail (type != NULL, FALSE);
273 : 10 : g_return_val_if_fail (supertype != NULL, FALSE);
274 : :
275 : 10 : G_LOCK (gio_xdgmime);
276 : 10 : g_begin_ignore_leaks ();
277 : 10 : res = xdg_mime_mime_type_subclass (type, supertype);
278 : 10 : g_end_ignore_leaks ();
279 : 10 : G_UNLOCK (gio_xdgmime);
280 : :
281 : 10 : return res;
282 : : }
283 : :
284 : : /**
285 : : * g_content_type_is_mime_type:
286 : : * @type: a content type string
287 : : * @mime_type: a mime type string
288 : : *
289 : : * Determines if @type is a subset of @mime_type.
290 : : * Convenience wrapper around g_content_type_is_a().
291 : : *
292 : : * Returns: %TRUE if @type is a kind of @mime_type,
293 : : * %FALSE otherwise.
294 : : *
295 : : * Since: 2.52
296 : : */
297 : : gboolean
298 : 1 : g_content_type_is_mime_type (const gchar *type,
299 : : const gchar *mime_type)
300 : : {
301 : 1 : return g_content_type_is_a (type, mime_type);
302 : : }
303 : :
304 : : /**
305 : : * g_content_type_is_unknown:
306 : : * @type: a content type string
307 : : *
308 : : * Checks if the content type is the generic "unknown" type.
309 : : * On UNIX this is the "application/octet-stream" mimetype,
310 : : * while on win32 it is "*" and on OSX it is a dynamic type
311 : : * or octet-stream.
312 : : *
313 : : * Returns: %TRUE if the type is the unknown type.
314 : : */
315 : : gboolean
316 : 1 : g_content_type_is_unknown (const gchar *type)
317 : : {
318 : 1 : g_return_val_if_fail (type != NULL, FALSE);
319 : :
320 : 1 : return strcmp (XDG_MIME_TYPE_UNKNOWN, type) == 0;
321 : : }
322 : :
323 : :
324 : : typedef enum {
325 : : MIME_TAG_TYPE_OTHER,
326 : : MIME_TAG_TYPE_COMMENT
327 : : } MimeTagType;
328 : :
329 : : typedef struct {
330 : : int current_type;
331 : : int current_lang_level;
332 : : int comment_lang_level;
333 : : char *comment;
334 : : } MimeParser;
335 : :
336 : :
337 : : static int
338 : 51 : language_level (const char *lang)
339 : : {
340 : : const char * const *lang_list;
341 : : int i;
342 : :
343 : : /* The returned list is sorted from most desirable to least
344 : : desirable and always contains the default locale "C". */
345 : 51 : lang_list = g_get_language_names ();
346 : :
347 [ + + ]: 202 : for (i = 0; lang_list[i]; i++)
348 [ + + ]: 152 : if (strcmp (lang_list[i], lang) == 0)
349 : 1 : return 1000-i;
350 : :
351 : 50 : return 0;
352 : : }
353 : :
354 : : static void
355 : 55 : mime_info_start_element (GMarkupParseContext *context,
356 : : const gchar *element_name,
357 : : const gchar **attribute_names,
358 : : const gchar **attribute_values,
359 : : gpointer user_data,
360 : : GError **error)
361 : : {
362 : : int i;
363 : : const char *lang;
364 : 55 : MimeParser *parser = user_data;
365 : :
366 [ + + ]: 55 : if (strcmp (element_name, "comment") == 0)
367 : : {
368 : 51 : lang = "C";
369 [ + + ]: 51 : for (i = 0; attribute_names[i]; i++)
370 [ + - ]: 50 : if (strcmp (attribute_names[i], "xml:lang") == 0)
371 : : {
372 : 50 : lang = attribute_values[i];
373 : 50 : break;
374 : : }
375 : :
376 : 51 : parser->current_lang_level = language_level (lang);
377 : 51 : parser->current_type = MIME_TAG_TYPE_COMMENT;
378 : : }
379 : : else
380 : 4 : parser->current_type = MIME_TAG_TYPE_OTHER;
381 : 55 : }
382 : :
383 : : static void
384 : 55 : mime_info_end_element (GMarkupParseContext *context,
385 : : const gchar *element_name,
386 : : gpointer user_data,
387 : : GError **error)
388 : : {
389 : 55 : MimeParser *parser = user_data;
390 : :
391 : 55 : parser->current_type = MIME_TAG_TYPE_OTHER;
392 : 55 : }
393 : :
394 : : static void
395 : 107 : mime_info_text (GMarkupParseContext *context,
396 : : const gchar *text,
397 : : gsize text_len,
398 : : gpointer user_data,
399 : : GError **error)
400 : : {
401 : 107 : MimeParser *parser = user_data;
402 : :
403 [ + + ]: 107 : if (parser->current_type == MIME_TAG_TYPE_COMMENT &&
404 [ + + ]: 51 : parser->current_lang_level > parser->comment_lang_level)
405 : : {
406 : 1 : g_free (parser->comment);
407 : 1 : parser->comment = g_strndup (text, text_len);
408 : 1 : parser->comment_lang_level = parser->current_lang_level;
409 : : }
410 : 107 : }
411 : :
412 : : static char *
413 : 3 : load_comment_for_mime_helper (const char *dir,
414 : : const char *basename)
415 : : {
416 : : GMarkupParseContext *context;
417 : : char *filename, *data;
418 : : gsize len;
419 : : gboolean res;
420 : 3 : MimeParser parse_data = {0};
421 : 3 : GMarkupParser parser = {
422 : : mime_info_start_element,
423 : : mime_info_end_element,
424 : : mime_info_text,
425 : : NULL,
426 : : NULL
427 : : };
428 : :
429 : 3 : filename = g_build_filename (dir, basename, NULL);
430 : :
431 : 3 : res = g_file_get_contents (filename, &data, &len, NULL);
432 : 3 : g_free (filename);
433 [ + + ]: 3 : if (!res)
434 : 2 : return NULL;
435 : :
436 : 1 : context = g_markup_parse_context_new (&parser, G_MARKUP_DEFAULT_FLAGS, &parse_data, NULL);
437 : 1 : res = g_markup_parse_context_parse (context, data, len, NULL);
438 : 1 : g_free (data);
439 : 1 : g_markup_parse_context_free (context);
440 : :
441 [ - + ]: 1 : if (!res)
442 : 0 : return NULL;
443 : :
444 : 1 : return parse_data.comment;
445 : : }
446 : :
447 : :
448 : : static char *
449 : 1 : load_comment_for_mime (const char *mimetype)
450 : : {
451 : : const char * const *dirs;
452 : : char *basename;
453 : : char *comment;
454 : : gsize i;
455 : :
456 : 1 : basename = g_strdup_printf ("%s.xml", mimetype);
457 : :
458 : 1 : dirs = g_content_type_get_mime_dirs ();
459 [ + - ]: 3 : for (i = 0; dirs[i] != NULL; i++)
460 : : {
461 : 3 : comment = load_comment_for_mime_helper (dirs[i], basename);
462 [ + + ]: 3 : if (comment)
463 : : {
464 : 1 : g_free (basename);
465 : 1 : return comment;
466 : : }
467 : : }
468 : 0 : g_free (basename);
469 : :
470 : 0 : return g_strdup_printf (_("%s type"), mimetype);
471 : : }
472 : :
473 : : /**
474 : : * g_content_type_get_description:
475 : : * @type: a content type string
476 : : *
477 : : * Gets the human readable description of the content type.
478 : : *
479 : : * Returns: a short description of the content type @type. Free the
480 : : * returned string with g_free()
481 : : */
482 : : gchar *
483 : 1 : g_content_type_get_description (const gchar *type)
484 : : {
485 : : static GHashTable *type_comment_cache = NULL;
486 : 1 : gchar *type_copy = NULL;
487 : : gchar *comment;
488 : :
489 : 1 : g_return_val_if_fail (type != NULL, NULL);
490 : :
491 : 1 : G_LOCK (gio_xdgmime);
492 : 1 : g_begin_ignore_leaks ();
493 : 1 : type = xdg_mime_unalias_mime_type (type);
494 : 1 : g_end_ignore_leaks ();
495 : :
496 [ + - ]: 1 : if (type_comment_cache == NULL)
497 : 1 : type_comment_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
498 : :
499 : 1 : comment = g_hash_table_lookup (type_comment_cache, type);
500 : 1 : comment = g_strdup (comment);
501 : :
502 [ - + ]: 1 : if (comment != NULL)
503 : : {
504 : 0 : G_UNLOCK (gio_xdgmime);
505 : 0 : return g_steal_pointer (&comment);
506 : : }
507 : :
508 : 1 : type_copy = g_strdup (type);
509 : :
510 : 1 : G_UNLOCK (gio_xdgmime);
511 : 1 : comment = load_comment_for_mime (type_copy);
512 : 1 : G_LOCK (gio_xdgmime);
513 : :
514 : 1 : g_hash_table_insert (type_comment_cache,
515 : : g_steal_pointer (&type_copy),
516 : 1 : g_strdup (comment));
517 : 1 : G_UNLOCK (gio_xdgmime);
518 : :
519 : 1 : return g_steal_pointer (&comment);
520 : : }
521 : :
522 : : /**
523 : : * g_content_type_get_mime_type:
524 : : * @type: a content type string
525 : : *
526 : : * Gets the mime type for the content type, if one is registered.
527 : : *
528 : : * Returns: (nullable) (transfer full): the registered mime type for the
529 : : * given @type, or %NULL if unknown; free with g_free().
530 : : */
531 : : char *
532 : 2 : g_content_type_get_mime_type (const char *type)
533 : : {
534 : 2 : g_return_val_if_fail (type != NULL, NULL);
535 : :
536 : 2 : return g_strdup (type);
537 : : }
538 : :
539 : : static GIcon *
540 : 156 : g_content_type_get_icon_internal (const gchar *type,
541 : : gboolean symbolic)
542 : : {
543 : : char *mimetype_icon;
544 : 156 : char *generic_mimetype_icon = NULL;
545 : : char *q;
546 : : char *icon_names[6];
547 : 156 : int n = 0;
548 : : GIcon *themed_icon;
549 : : const char *xdg_icon;
550 : : int i;
551 : :
552 : 156 : g_return_val_if_fail (type != NULL, NULL);
553 : :
554 : 156 : G_LOCK (gio_xdgmime);
555 : 156 : g_begin_ignore_leaks ();
556 : 156 : xdg_icon = xdg_mime_get_icon (type);
557 : 156 : g_end_ignore_leaks ();
558 : 156 : G_UNLOCK (gio_xdgmime);
559 : :
560 [ - + ]: 156 : if (xdg_icon)
561 : 0 : icon_names[n++] = g_strdup (xdg_icon);
562 : :
563 : 156 : mimetype_icon = g_strdup (type);
564 [ + + ]: 312 : while ((q = strchr (mimetype_icon, '/')) != NULL)
565 : 156 : *q = '-';
566 : :
567 : 156 : icon_names[n++] = mimetype_icon;
568 : :
569 : 156 : generic_mimetype_icon = g_content_type_get_generic_icon_name (type);
570 [ + - ]: 156 : if (generic_mimetype_icon)
571 : 156 : icon_names[n++] = generic_mimetype_icon;
572 : :
573 [ + + ]: 156 : if (symbolic)
574 : : {
575 [ + + ]: 234 : for (i = 0; i < n; i++)
576 : : {
577 : 156 : icon_names[n + i] = icon_names[i];
578 : 156 : icon_names[i] = g_strconcat (icon_names[i], "-symbolic", NULL);
579 : : }
580 : :
581 : 78 : n += n;
582 : : }
583 : :
584 : 156 : themed_icon = g_themed_icon_new_from_names (icon_names, n);
585 : :
586 [ + + ]: 624 : for (i = 0; i < n; i++)
587 : 468 : g_free (icon_names[i]);
588 : :
589 : 156 : return themed_icon;
590 : : }
591 : :
592 : : /**
593 : : * g_content_type_get_icon:
594 : : * @type: a content type string
595 : : *
596 : : * Gets the icon for a content type.
597 : : *
598 : : * Returns: (transfer full): #GIcon corresponding to the content type. Free the returned
599 : : * object with g_object_unref()
600 : : */
601 : : GIcon *
602 : 78 : g_content_type_get_icon (const gchar *type)
603 : : {
604 : 78 : return g_content_type_get_icon_internal (type, FALSE);
605 : : }
606 : :
607 : : /**
608 : : * g_content_type_get_symbolic_icon:
609 : : * @type: a content type string
610 : : *
611 : : * Gets the symbolic icon for a content type.
612 : : *
613 : : * Returns: (transfer full): symbolic #GIcon corresponding to the content type.
614 : : * Free the returned object with g_object_unref()
615 : : *
616 : : * Since: 2.34
617 : : */
618 : : GIcon *
619 : 78 : g_content_type_get_symbolic_icon (const gchar *type)
620 : : {
621 : 78 : return g_content_type_get_icon_internal (type, TRUE);
622 : : }
623 : :
624 : : /**
625 : : * g_content_type_get_generic_icon_name:
626 : : * @type: a content type string
627 : : *
628 : : * Gets the generic icon name for a content type.
629 : : *
630 : : * See the
631 : : * [shared-mime-info](http://www.freedesktop.org/wiki/Specifications/shared-mime-info-spec)
632 : : * specification for more on the generic icon name.
633 : : *
634 : : * Returns: (nullable): the registered generic icon name for the given @type,
635 : : * or %NULL if unknown. Free with g_free()
636 : : *
637 : : * Since: 2.34
638 : : */
639 : : gchar *
640 : 156 : g_content_type_get_generic_icon_name (const gchar *type)
641 : : {
642 : : const gchar *xdg_icon_name;
643 : : gchar *icon_name;
644 : :
645 : 156 : g_return_val_if_fail (type != NULL, NULL);
646 : :
647 : 156 : G_LOCK (gio_xdgmime);
648 : 156 : g_begin_ignore_leaks ();
649 : 156 : xdg_icon_name = xdg_mime_get_generic_icon (type);
650 : 156 : g_end_ignore_leaks ();
651 : 156 : G_UNLOCK (gio_xdgmime);
652 : :
653 [ + + ]: 156 : if (!xdg_icon_name)
654 : : {
655 : : const char *p;
656 : 106 : const char *suffix = "-x-generic";
657 : :
658 : 106 : p = strchr (type, '/');
659 [ - + ]: 106 : if (p == NULL)
660 : 0 : p = type + strlen (type);
661 : :
662 : 106 : icon_name = g_malloc (p - type + strlen (suffix) + 1);
663 : 106 : memcpy (icon_name, type, p - type);
664 : 106 : memcpy (icon_name + (p - type), suffix, strlen (suffix));
665 : 106 : icon_name[(p - type) + strlen (suffix)] = 0;
666 : : }
667 : : else
668 : : {
669 : 50 : icon_name = g_strdup (xdg_icon_name);
670 : : }
671 : :
672 : 156 : return icon_name;
673 : : }
674 : :
675 : : /**
676 : : * g_content_type_can_be_executable:
677 : : * @type: a content type string
678 : : *
679 : : * Checks if a content type can be executable. Note that for instance
680 : : * things like text files can be executables (i.e. scripts and batch files).
681 : : *
682 : : * Returns: %TRUE if the file type corresponds to a type that
683 : : * can be executable, %FALSE otherwise.
684 : : */
685 : : gboolean
686 : 3 : g_content_type_can_be_executable (const gchar *type)
687 : : {
688 : 3 : g_return_val_if_fail (type != NULL, FALSE);
689 : :
690 [ + + + + ]: 5 : if (g_content_type_is_a (type, "application/x-executable") ||
691 : 2 : g_content_type_is_a (type, "text/plain"))
692 : 2 : return TRUE;
693 : :
694 : 1 : return FALSE;
695 : : }
696 : :
697 : : static gboolean
698 : 7 : looks_like_text (const guchar *data, gsize data_size)
699 : : {
700 : : gsize i;
701 : : char c;
702 : :
703 [ + - ]: 18 : for (i = 0; i < data_size; i++)
704 : : {
705 : 18 : c = data[i];
706 : :
707 [ + + ]: 18 : if (g_ascii_iscntrl (c) &&
708 [ + - + - ]: 7 : !g_ascii_isspace (c) &&
709 : : c != '\b')
710 : 7 : return FALSE;
711 : : }
712 : 0 : return TRUE;
713 : : }
714 : :
715 : : /**
716 : : * g_content_type_from_mime_type:
717 : : * @mime_type: a mime type string
718 : : *
719 : : * Tries to find a content type based on the mime type name.
720 : : *
721 : : * Returns: (nullable): Newly allocated string with content type or
722 : : * %NULL. Free with g_free()
723 : : *
724 : : * Since: 2.18
725 : : **/
726 : : gchar *
727 : 167 : g_content_type_from_mime_type (const gchar *mime_type)
728 : : {
729 : : char *umime;
730 : :
731 : 167 : g_return_val_if_fail (mime_type != NULL, NULL);
732 : :
733 : 167 : G_LOCK (gio_xdgmime);
734 : 167 : g_begin_ignore_leaks ();
735 : : /* mime type and content type are same on unixes */
736 : 167 : umime = g_strdup (xdg_mime_unalias_mime_type (mime_type));
737 : 167 : g_end_ignore_leaks ();
738 : 167 : G_UNLOCK (gio_xdgmime);
739 : :
740 : 167 : return umime;
741 : : }
742 : :
743 : : /**
744 : : * g_content_type_guess:
745 : : * @filename: (nullable) (type filename): a path, or %NULL
746 : : * @data: (nullable) (array length=data_size): a stream of data, or %NULL
747 : : * @data_size: the size of @data
748 : : * @result_uncertain: (out) (optional): return location for the certainty
749 : : * of the result, or %NULL
750 : : *
751 : : * Guesses the content type based on example data. If the function is
752 : : * uncertain, @result_uncertain will be set to %TRUE. Either @filename
753 : : * or @data may be %NULL, in which case the guess will be based solely
754 : : * on the other argument.
755 : : *
756 : : * Returns: a string indicating a guessed content type for the
757 : : * given data. Free with g_free()
758 : : */
759 : : gchar *
760 : 44 : g_content_type_guess (const gchar *filename,
761 : : const guchar *data,
762 : : gsize data_size,
763 : : gboolean *result_uncertain)
764 : : {
765 : : char *basename;
766 : : const char *name_mimetypes[10], *sniffed_mimetype;
767 : : char *mimetype;
768 : : int i;
769 : : int n_name_mimetypes;
770 : : int sniffed_prio;
771 : :
772 : 44 : sniffed_prio = 0;
773 : 44 : n_name_mimetypes = 0;
774 : 44 : sniffed_mimetype = XDG_MIME_TYPE_UNKNOWN;
775 : :
776 [ + + ]: 44 : if (result_uncertain)
777 : 33 : *result_uncertain = FALSE;
778 : :
779 : : /* our test suite and potentially other code used -1 in the past, which is
780 : : * not documented and not allowed; guard against that */
781 : 44 : g_return_val_if_fail (data_size != (gsize) -1, g_strdup (XDG_MIME_TYPE_UNKNOWN));
782 : :
783 : 44 : G_LOCK (gio_xdgmime);
784 : 44 : g_begin_ignore_leaks ();
785 : :
786 [ + + ]: 44 : if (filename)
787 : : {
788 : 40 : i = strlen (filename);
789 [ + - + + ]: 40 : if (i > 0 && filename[i - 1] == '/')
790 : : {
791 : 1 : name_mimetypes[0] = "inode/directory";
792 : 1 : name_mimetypes[1] = NULL;
793 : 1 : n_name_mimetypes = 1;
794 [ + - ]: 1 : if (result_uncertain)
795 : 1 : *result_uncertain = TRUE;
796 : : }
797 : : else
798 : : {
799 : 39 : basename = g_path_get_basename (filename);
800 : 39 : n_name_mimetypes = xdg_mime_get_mime_types_from_file_name (basename, name_mimetypes, 10);
801 : 39 : g_free (basename);
802 : : }
803 : : }
804 : :
805 : : /* Got an extension match, and no conflicts. This is it. */
806 [ + + ]: 44 : if (n_name_mimetypes == 1)
807 : : {
808 : 5 : gchar *s = g_strdup (name_mimetypes[0]);
809 : 5 : g_end_ignore_leaks ();
810 : 5 : G_UNLOCK (gio_xdgmime);
811 : 5 : return s;
812 : : }
813 : :
814 [ + + ]: 39 : if (data)
815 : : {
816 : 19 : sniffed_mimetype = xdg_mime_get_mime_type_for_data (data, data_size, &sniffed_prio);
817 [ + + + - ]: 19 : if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN &&
818 [ - + ]: 7 : data &&
819 : 7 : looks_like_text (data, data_size))
820 : 0 : sniffed_mimetype = "text/plain";
821 : :
822 : : /* For security reasons we don't ever want to sniff desktop files
823 : : * where we know the filename and it doesn't have a .desktop extension.
824 : : * This is because desktop files allow executing any application and
825 : : * we don't want to make it possible to hide them looking like something
826 : : * else.
827 : : */
828 [ + + ]: 19 : if (filename != NULL &&
829 [ + + ]: 15 : strcmp (sniffed_mimetype, "application/x-desktop") == 0)
830 : 1 : sniffed_mimetype = "text/plain";
831 : : }
832 : :
833 [ + + ]: 39 : if (n_name_mimetypes == 0)
834 : : {
835 [ + + + + ]: 35 : if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN &&
836 : : result_uncertain)
837 : 20 : *result_uncertain = TRUE;
838 : :
839 : 35 : mimetype = g_strdup (sniffed_mimetype);
840 : : }
841 : : else
842 : : {
843 : 4 : mimetype = NULL;
844 [ + + ]: 4 : if (sniffed_mimetype != XDG_MIME_TYPE_UNKNOWN)
845 : : {
846 [ - + ]: 3 : if (sniffed_prio >= 80) /* High priority sniffing match, use that */
847 : 0 : mimetype = g_strdup (sniffed_mimetype);
848 : : else
849 : : {
850 : : /* There are conflicts between the name matches and we
851 : : * have a sniffed type, use that as a tie breaker.
852 : : */
853 [ + - ]: 6 : for (i = 0; i < n_name_mimetypes; i++)
854 : : {
855 [ + + ]: 6 : if ( xdg_mime_mime_type_subclass (name_mimetypes[i], sniffed_mimetype))
856 : : {
857 : : /* This nametype match is derived from (or the same as)
858 : : * the sniffed type). This is probably it.
859 : : */
860 : 3 : mimetype = g_strdup (name_mimetypes[i]);
861 : 3 : break;
862 : : }
863 : : }
864 : : }
865 : : }
866 : :
867 [ + + ]: 4 : if (mimetype == NULL)
868 : : {
869 : : /* Conflicts, and sniffed type was no help or not there.
870 : : * Guess on the first one
871 : : */
872 : 1 : mimetype = g_strdup (name_mimetypes[0]);
873 [ + - ]: 1 : if (result_uncertain)
874 : 1 : *result_uncertain = TRUE;
875 : : }
876 : : }
877 : :
878 : 39 : g_end_ignore_leaks ();
879 : 39 : G_UNLOCK (gio_xdgmime);
880 : :
881 : 39 : return mimetype;
882 : : }
883 : :
884 : : static void
885 : 14 : enumerate_mimetypes_subdir (const char *dir,
886 : : const char *prefix,
887 : : GHashTable *mimetypes)
888 : : {
889 : : DIR *d;
890 : : struct dirent *ent;
891 : : char *mimetype;
892 : :
893 : 14 : d = opendir (dir);
894 [ + - ]: 14 : if (d)
895 : : {
896 [ + + ]: 1031 : while ((ent = readdir (d)) != NULL)
897 : : {
898 [ + - - + : 1003 : if (g_str_has_suffix (ent->d_name, ".xml"))
+ + + + ]
899 : : {
900 : 851 : mimetype = g_strdup_printf ("%s/%.*s", prefix, (int) strlen (ent->d_name) - 4, ent->d_name);
901 : 851 : g_hash_table_replace (mimetypes, mimetype, NULL);
902 : : }
903 : : }
904 : 14 : closedir (d);
905 : : }
906 : 14 : }
907 : :
908 : : static void
909 : 3 : enumerate_mimetypes_dir (const char *dir,
910 : : GHashTable *mimetypes)
911 : : {
912 : : DIR *d;
913 : : struct dirent *ent;
914 : : const char *mimedir;
915 : : char *name;
916 : :
917 : 3 : mimedir = dir;
918 : :
919 : 3 : d = opendir (mimedir);
920 [ + + ]: 3 : if (d)
921 : : {
922 [ + + ]: 29 : while ((ent = readdir (d)) != NULL)
923 : : {
924 [ + + ]: 27 : if (strcmp (ent->d_name, "packages") != 0)
925 : : {
926 : 26 : name = g_build_filename (mimedir, ent->d_name, NULL);
927 [ + + ]: 26 : if (g_file_test (name, G_FILE_TEST_IS_DIR))
928 : 14 : enumerate_mimetypes_subdir (name, ent->d_name, mimetypes);
929 : 26 : g_free (name);
930 : : }
931 : : }
932 : 1 : closedir (d);
933 : : }
934 : 3 : }
935 : :
936 : : /**
937 : : * g_content_types_get_registered:
938 : : *
939 : : * Gets a list of strings containing all the registered content types
940 : : * known to the system. The list and its data should be freed using
941 : : * `g_list_free_full (list, g_free)`.
942 : : *
943 : : * Returns: (element-type utf8) (transfer full): list of the registered
944 : : * content types
945 : : */
946 : : GList *
947 : 1 : g_content_types_get_registered (void)
948 : : {
949 : : const char * const *dirs;
950 : : GHashTable *mimetypes;
951 : : GHashTableIter iter;
952 : : gpointer key;
953 : : gsize i;
954 : : GList *l;
955 : :
956 : 1 : mimetypes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
957 : :
958 : 1 : dirs = g_content_type_get_mime_dirs ();
959 [ + + ]: 4 : for (i = 0; dirs[i] != NULL; i++)
960 : 3 : enumerate_mimetypes_dir (dirs[i], mimetypes);
961 : :
962 : 1 : l = NULL;
963 : 1 : g_hash_table_iter_init (&iter, mimetypes);
964 [ + + ]: 852 : while (g_hash_table_iter_next (&iter, &key, NULL))
965 : : {
966 : 851 : l = g_list_prepend (l, key);
967 : 851 : g_hash_table_iter_steal (&iter);
968 : : }
969 : :
970 : 1 : g_hash_table_destroy (mimetypes);
971 : :
972 : 1 : return l;
973 : : }
974 : :
975 : :
976 : : /* tree magic data */
977 : : static GList *tree_matches = NULL;
978 : : static gboolean need_reload = FALSE;
979 : :
980 : : G_LOCK_DEFINE_STATIC (gio_treemagic);
981 : :
982 : : typedef struct
983 : : {
984 : : gchar *path;
985 : : GFileType type;
986 : : guint match_case : 1;
987 : : guint executable : 1;
988 : : guint non_empty : 1;
989 : : guint on_disc : 1;
990 : : gchar *mimetype;
991 : : GList *matches;
992 : : } TreeMatchlet;
993 : :
994 : : typedef struct
995 : : {
996 : : gchar *contenttype;
997 : : gint priority;
998 : : GList *matches;
999 : : } TreeMatch;
1000 : :
1001 : :
1002 : : static void
1003 : 0 : tree_matchlet_free (TreeMatchlet *matchlet)
1004 : : {
1005 : 0 : g_list_free_full (matchlet->matches, (GDestroyNotify) tree_matchlet_free);
1006 : 0 : g_free (matchlet->path);
1007 : 0 : g_free (matchlet->mimetype);
1008 : 0 : g_slice_free (TreeMatchlet, matchlet);
1009 : 0 : }
1010 : :
1011 : : static void
1012 : 0 : tree_match_free (TreeMatch *match)
1013 : : {
1014 : 0 : g_list_free_full (match->matches, (GDestroyNotify) tree_matchlet_free);
1015 : 0 : g_free (match->contenttype);
1016 : 0 : g_slice_free (TreeMatch, match);
1017 : 0 : }
1018 : :
1019 : : static TreeMatch *
1020 : 12 : parse_header (gchar *line)
1021 : : {
1022 : : gint len;
1023 : : gchar *s;
1024 : : TreeMatch *match;
1025 : :
1026 : 12 : len = strlen (line);
1027 : :
1028 [ + - - + ]: 12 : if (line[0] != '[' || line[len - 1] != ']')
1029 : 0 : return NULL;
1030 : :
1031 : 12 : line[len - 1] = 0;
1032 : 12 : s = strchr (line, ':');
1033 [ - + ]: 12 : if (s == NULL)
1034 : 0 : return NULL;
1035 : :
1036 : 12 : match = g_slice_new0 (TreeMatch);
1037 : 12 : match->priority = atoi (line + 1);
1038 : 12 : match->contenttype = g_strdup (s + 1);
1039 : :
1040 : 12 : return match;
1041 : : }
1042 : :
1043 : : static TreeMatchlet *
1044 : 25 : parse_match_line (gchar *line,
1045 : : gint *depth)
1046 : : {
1047 : : gchar *s, *p;
1048 : : TreeMatchlet *matchlet;
1049 : : gchar **parts;
1050 : : gint i;
1051 : :
1052 : 25 : matchlet = g_slice_new0 (TreeMatchlet);
1053 : :
1054 [ + - ]: 25 : if (line[0] == '>')
1055 : : {
1056 : 25 : *depth = 0;
1057 : 25 : s = line;
1058 : : }
1059 : : else
1060 : : {
1061 : 0 : *depth = atoi (line);
1062 : 0 : s = strchr (line, '>');
1063 [ # # ]: 0 : if (s == NULL)
1064 : 0 : goto handle_error;
1065 : : }
1066 : 25 : s += 2;
1067 : 25 : p = strchr (s, '"');
1068 [ - + ]: 25 : if (p == NULL)
1069 : 0 : goto handle_error;
1070 : 25 : *p = 0;
1071 : :
1072 : 25 : matchlet->path = g_strdup (s);
1073 : 25 : s = p + 1;
1074 : 25 : parts = g_strsplit (s, ",", 0);
1075 [ + + ]: 25 : if (strcmp (parts[0], "=file") == 0)
1076 : 16 : matchlet->type = G_FILE_TYPE_REGULAR;
1077 [ + + ]: 9 : else if (strcmp (parts[0], "=directory") == 0)
1078 : 8 : matchlet->type = G_FILE_TYPE_DIRECTORY;
1079 [ - + ]: 1 : else if (strcmp (parts[0], "=link") == 0)
1080 : 0 : matchlet->type = G_FILE_TYPE_SYMBOLIC_LINK;
1081 : : else
1082 : 1 : matchlet->type = G_FILE_TYPE_UNKNOWN;
1083 [ + + ]: 41 : for (i = 1; parts[i]; i++)
1084 : : {
1085 [ + + ]: 16 : if (strcmp (parts[i], "executable") == 0)
1086 : 1 : matchlet->executable = 1;
1087 [ + + ]: 15 : else if (strcmp (parts[i], "match-case") == 0)
1088 : 7 : matchlet->match_case = 1;
1089 [ + - ]: 8 : else if (strcmp (parts[i], "non-empty") == 0)
1090 : 8 : matchlet->non_empty = 1;
1091 [ # # ]: 0 : else if (strcmp (parts[i], "on-disc") == 0)
1092 : 0 : matchlet->on_disc = 1;
1093 : : else
1094 : 0 : matchlet->mimetype = g_strdup (parts[i]);
1095 : : }
1096 : :
1097 : 25 : g_strfreev (parts);
1098 : :
1099 : 25 : return matchlet;
1100 : :
1101 : 0 : handle_error:
1102 : 0 : g_slice_free (TreeMatchlet, matchlet);
1103 : 0 : return NULL;
1104 : : }
1105 : :
1106 : : static gint
1107 : 11 : cmp_match (gconstpointer a, gconstpointer b)
1108 : : {
1109 : 11 : const TreeMatch *aa = (const TreeMatch *)a;
1110 : 11 : const TreeMatch *bb = (const TreeMatch *)b;
1111 : :
1112 : 11 : return bb->priority - aa->priority;
1113 : : }
1114 : :
1115 : : static void
1116 : 12 : insert_match (TreeMatch *match)
1117 : : {
1118 : 12 : tree_matches = g_list_insert_sorted (tree_matches, match, cmp_match);
1119 : 12 : }
1120 : :
1121 : : static void
1122 : 25 : insert_matchlet (TreeMatch *match,
1123 : : TreeMatchlet *matchlet,
1124 : : gint depth)
1125 : : {
1126 [ + - ]: 25 : if (depth == 0)
1127 : 25 : match->matches = g_list_append (match->matches, matchlet);
1128 : : else
1129 : : {
1130 : : GList *last;
1131 : : TreeMatchlet *m;
1132 : :
1133 : 0 : last = g_list_last (match->matches);
1134 [ # # ]: 0 : if (!last)
1135 : : {
1136 : 0 : tree_matchlet_free (matchlet);
1137 : 0 : g_warning ("can't insert tree matchlet at depth %d", depth);
1138 : 0 : return;
1139 : : }
1140 : :
1141 : 0 : m = (TreeMatchlet *) last->data;
1142 [ # # ]: 0 : while (--depth > 0)
1143 : : {
1144 : 0 : last = g_list_last (m->matches);
1145 [ # # ]: 0 : if (!last)
1146 : : {
1147 : 0 : tree_matchlet_free (matchlet);
1148 : 0 : g_warning ("can't insert tree matchlet at depth %d", depth);
1149 : 0 : return;
1150 : : }
1151 : :
1152 : 0 : m = (TreeMatchlet *) last->data;
1153 : : }
1154 : 0 : m->matches = g_list_append (m->matches, matchlet);
1155 : : }
1156 : : }
1157 : :
1158 : : static void
1159 : 3 : read_tree_magic_from_directory (const gchar *prefix)
1160 : : {
1161 : : gchar *filename;
1162 : : gchar *text;
1163 : : gsize len;
1164 : : gchar **lines;
1165 : : gsize i;
1166 : : TreeMatch *match;
1167 : : TreeMatchlet *matchlet;
1168 : : gint depth;
1169 : :
1170 : 3 : filename = g_build_filename (prefix, "treemagic", NULL);
1171 : :
1172 [ + + ]: 3 : if (g_file_get_contents (filename, &text, &len, NULL))
1173 : : {
1174 [ + - ]: 1 : if (strcmp (text, "MIME-TreeMagic") == 0)
1175 : : {
1176 : 1 : lines = g_strsplit (text + strlen ("MIME-TreeMagic") + 2, "\n", 0);
1177 : 1 : match = NULL;
1178 [ + - + + ]: 38 : for (i = 0; lines[i] && lines[i][0]; i++)
1179 : : {
1180 [ + + + - ]: 37 : if (lines[i][0] == '[' && (match = parse_header (lines[i])) != NULL)
1181 : : {
1182 : 12 : insert_match (match);
1183 : : }
1184 [ + - ]: 25 : else if (match != NULL)
1185 : : {
1186 : 25 : matchlet = parse_match_line (lines[i], &depth);
1187 [ - + ]: 25 : if (matchlet == NULL)
1188 : : {
1189 : 0 : g_warning ("%s: body corrupt; skipping", filename);
1190 : 0 : break;
1191 : : }
1192 : 25 : insert_matchlet (match, matchlet, depth);
1193 : : }
1194 : : else
1195 : : {
1196 : 0 : g_warning ("%s: header corrupt; skipping", filename);
1197 : 0 : break;
1198 : : }
1199 : : }
1200 : :
1201 : 1 : g_strfreev (lines);
1202 : : }
1203 : : else
1204 : 0 : g_warning ("%s: header not found, skipping", filename);
1205 : :
1206 : 1 : g_free (text);
1207 : : }
1208 : :
1209 : 3 : g_free (filename);
1210 : 3 : }
1211 : :
1212 : : static void
1213 : 2 : tree_magic_schedule_reload (void)
1214 : : {
1215 : 2 : need_reload = TRUE;
1216 : 2 : }
1217 : :
1218 : : static void
1219 : 0 : xdg_mime_reload (void *user_data)
1220 : : {
1221 : 0 : tree_magic_schedule_reload ();
1222 : 0 : }
1223 : :
1224 : : static void
1225 : 1 : tree_magic_shutdown (void)
1226 : : {
1227 : 1 : g_list_free_full (tree_matches, (GDestroyNotify) tree_match_free);
1228 : 1 : tree_matches = NULL;
1229 : 1 : }
1230 : :
1231 : : static void
1232 : 4 : tree_magic_init (void)
1233 : : {
1234 : : static gboolean initialized = FALSE;
1235 : : gsize i;
1236 : :
1237 [ + + ]: 4 : if (!initialized)
1238 : : {
1239 : 1 : initialized = TRUE;
1240 : :
1241 : 1 : xdg_mime_register_reload_callback (xdg_mime_reload, NULL, NULL);
1242 : 1 : need_reload = TRUE;
1243 : : }
1244 : :
1245 [ + + ]: 4 : if (need_reload)
1246 : : {
1247 : : const char * const *dirs;
1248 : :
1249 : 1 : need_reload = FALSE;
1250 : :
1251 : 1 : tree_magic_shutdown ();
1252 : :
1253 : 1 : dirs = g_content_type_get_mime_dirs ();
1254 [ + + ]: 4 : for (i = 0; dirs[i] != NULL; i++)
1255 : 3 : read_tree_magic_from_directory (dirs[i]);
1256 : : }
1257 : 4 : }
1258 : :
1259 : : /* a filtering enumerator */
1260 : :
1261 : : typedef struct
1262 : : {
1263 : : gchar *path;
1264 : : gint depth;
1265 : : gboolean ignore_case;
1266 : : gchar **components;
1267 : : gchar **case_components;
1268 : : GFileEnumerator **enumerators;
1269 : : GFile **children;
1270 : : } Enumerator;
1271 : :
1272 : : static gboolean
1273 : 99 : component_match (Enumerator *e,
1274 : : gint depth,
1275 : : const gchar *name)
1276 : : {
1277 : : gchar *case_folded, *key, *utf8_name;
1278 : : gboolean found;
1279 : :
1280 [ + + ]: 99 : if (strcmp (name, e->components[depth]) == 0)
1281 : 2 : return TRUE;
1282 : :
1283 [ + + ]: 97 : if (!e->ignore_case)
1284 : 27 : return FALSE;
1285 : :
1286 : 70 : utf8_name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
1287 [ + + ]: 70 : if (utf8_name == NULL)
1288 : 18 : utf8_name = g_utf8_make_valid (name, -1);
1289 : :
1290 : 70 : case_folded = g_utf8_casefold (utf8_name, -1);
1291 : 70 : key = g_utf8_collate_key (case_folded, -1);
1292 : :
1293 : 70 : found = strcmp (key, e->case_components[depth]) == 0;
1294 : :
1295 : 70 : g_free (utf8_name);
1296 : 70 : g_free (case_folded);
1297 : 70 : g_free (key);
1298 : :
1299 : 70 : return found;
1300 : : }
1301 : :
1302 : : static GFile *
1303 : 155 : next_match_recurse (Enumerator *e,
1304 : : gint depth)
1305 : : {
1306 : : GFile *file;
1307 : : GFileInfo *info;
1308 : : const gchar *name;
1309 : :
1310 : : while (TRUE)
1311 : : {
1312 [ + + ]: 251 : if (e->enumerators[depth] == NULL)
1313 : : {
1314 [ + + ]: 152 : if (depth > 0)
1315 : : {
1316 : 56 : file = next_match_recurse (e, depth - 1);
1317 [ - + ]: 56 : if (file)
1318 : : {
1319 : 0 : e->children[depth] = file;
1320 : 0 : e->enumerators[depth] = g_file_enumerate_children (file,
1321 : : G_FILE_ATTRIBUTE_STANDARD_NAME,
1322 : : G_FILE_QUERY_INFO_NONE,
1323 : : NULL,
1324 : : NULL);
1325 : : }
1326 : : }
1327 [ + - ]: 152 : if (e->enumerators[depth] == NULL)
1328 : 152 : return NULL;
1329 : : }
1330 : :
1331 [ + + ]: 195 : while ((info = g_file_enumerator_next_file (e->enumerators[depth], NULL, NULL)))
1332 : : {
1333 : 99 : name = g_file_info_get_name (info);
1334 [ + + ]: 99 : if (component_match (e, depth, name))
1335 : : {
1336 : 3 : file = g_file_get_child (e->children[depth], name);
1337 : 3 : g_object_unref (info);
1338 : 3 : return file;
1339 : : }
1340 : 96 : g_object_unref (info);
1341 : : }
1342 : :
1343 : 96 : g_object_unref (e->enumerators[depth]);
1344 : 96 : e->enumerators[depth] = NULL;
1345 : 96 : g_object_unref (e->children[depth]);
1346 : 96 : e->children[depth] = NULL;
1347 : : }
1348 : : }
1349 : :
1350 : : static GFile *
1351 : 99 : enumerator_next (Enumerator *e)
1352 : : {
1353 : 99 : return next_match_recurse (e, e->depth - 1);
1354 : : }
1355 : :
1356 : : static Enumerator *
1357 : 99 : enumerator_new (GFile *root,
1358 : : const char *path,
1359 : : gboolean ignore_case)
1360 : : {
1361 : : Enumerator *e;
1362 : : gint i;
1363 : : gchar *case_folded;
1364 : :
1365 : 99 : e = g_new0 (Enumerator, 1);
1366 : 99 : e->path = g_strdup (path);
1367 : 99 : e->ignore_case = ignore_case;
1368 : :
1369 : 99 : e->components = g_strsplit (e->path, G_DIR_SEPARATOR_S, -1);
1370 : 99 : e->depth = g_strv_length (e->components);
1371 [ + + ]: 99 : if (e->ignore_case)
1372 : : {
1373 : 71 : e->case_components = g_new0 (char *, e->depth + 1);
1374 [ + + ]: 182 : for (i = 0; e->components[i]; i++)
1375 : : {
1376 : 111 : case_folded = g_utf8_casefold (e->components[i], -1);
1377 : 111 : e->case_components[i] = g_utf8_collate_key (case_folded, -1);
1378 : 111 : g_free (case_folded);
1379 : : }
1380 : : }
1381 : :
1382 : 99 : e->children = g_new0 (GFile *, e->depth);
1383 : 99 : e->children[0] = g_object_ref (root);
1384 : 99 : e->enumerators = g_new0 (GFileEnumerator *, e->depth);
1385 : 99 : e->enumerators[0] = g_file_enumerate_children (root,
1386 : : G_FILE_ATTRIBUTE_STANDARD_NAME,
1387 : : G_FILE_QUERY_INFO_NONE,
1388 : : NULL,
1389 : : NULL);
1390 : :
1391 : 99 : return e;
1392 : : }
1393 : :
1394 : : static void
1395 : 99 : enumerator_free (Enumerator *e)
1396 : : {
1397 : : gint i;
1398 : :
1399 [ + + ]: 254 : for (i = 0; i < e->depth; i++)
1400 : : {
1401 [ + + ]: 155 : if (e->enumerators[i])
1402 : 3 : g_object_unref (e->enumerators[i]);
1403 [ + + ]: 155 : if (e->children[i])
1404 : 3 : g_object_unref (e->children[i]);
1405 : : }
1406 : :
1407 : 99 : g_free (e->enumerators);
1408 : 99 : g_free (e->children);
1409 : 99 : g_strfreev (e->components);
1410 [ + + ]: 99 : if (e->case_components)
1411 : 71 : g_strfreev (e->case_components);
1412 : 99 : g_free (e->path);
1413 : 99 : g_free (e);
1414 : 99 : }
1415 : :
1416 : : static gboolean
1417 : 99 : matchlet_match (TreeMatchlet *matchlet,
1418 : : GFile *root)
1419 : : {
1420 : : GFile *file;
1421 : : GFileInfo *info;
1422 : : gboolean result;
1423 : : const gchar *attrs;
1424 : : Enumerator *e;
1425 : : GList *l;
1426 : :
1427 : 99 : e = enumerator_new (root, matchlet->path, !matchlet->match_case);
1428 : :
1429 : : do
1430 : : {
1431 : 99 : file = enumerator_next (e);
1432 [ + + ]: 99 : if (!file)
1433 : : {
1434 : 96 : enumerator_free (e);
1435 : 96 : return FALSE;
1436 : : }
1437 : :
1438 [ - + ]: 3 : if (matchlet->mimetype)
1439 : 0 : attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1440 : : G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ","
1441 : : G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
1442 : : else
1443 : 3 : attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
1444 : : G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE;
1445 : 3 : info = g_file_query_info (file,
1446 : : attrs,
1447 : : G_FILE_QUERY_INFO_NONE,
1448 : : NULL,
1449 : : NULL);
1450 [ + - ]: 3 : if (info)
1451 : : {
1452 : 3 : result = TRUE;
1453 : :
1454 [ + - ]: 3 : if (matchlet->type != G_FILE_TYPE_UNKNOWN &&
1455 [ - + ]: 3 : g_file_info_get_file_type (info) != matchlet->type)
1456 : 0 : result = FALSE;
1457 : :
1458 [ + + - + ]: 4 : if (matchlet->executable &&
1459 : 1 : !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
1460 : 0 : result = FALSE;
1461 : : }
1462 : : else
1463 : 0 : result = FALSE;
1464 : :
1465 [ + - + + ]: 3 : if (result && matchlet->non_empty)
1466 : : {
1467 : : GFileEnumerator *child_enum;
1468 : : GFileInfo *child_info;
1469 : :
1470 : 1 : child_enum = g_file_enumerate_children (file,
1471 : : G_FILE_ATTRIBUTE_STANDARD_NAME,
1472 : : G_FILE_QUERY_INFO_NONE,
1473 : : NULL,
1474 : : NULL);
1475 : :
1476 [ + - ]: 1 : if (child_enum)
1477 : : {
1478 : 1 : child_info = g_file_enumerator_next_file (child_enum, NULL, NULL);
1479 [ + - ]: 1 : if (child_info)
1480 : 1 : g_object_unref (child_info);
1481 : : else
1482 : 0 : result = FALSE;
1483 : 1 : g_object_unref (child_enum);
1484 : : }
1485 : : else
1486 : 0 : result = FALSE;
1487 : : }
1488 : :
1489 [ + - - + ]: 3 : if (result && matchlet->mimetype)
1490 : : {
1491 [ # # ]: 0 : if (strcmp (matchlet->mimetype, g_file_info_get_content_type (info)) != 0)
1492 : 0 : result = FALSE;
1493 : : }
1494 : :
1495 [ + - ]: 3 : if (info)
1496 : 3 : g_object_unref (info);
1497 : 3 : g_object_unref (file);
1498 : : }
1499 [ - + ]: 3 : while (!result);
1500 : :
1501 : 3 : enumerator_free (e);
1502 : :
1503 [ + - ]: 3 : if (!matchlet->matches)
1504 : 3 : return TRUE;
1505 : :
1506 [ # # ]: 0 : for (l = matchlet->matches; l; l = l->next)
1507 : : {
1508 : : TreeMatchlet *submatchlet;
1509 : :
1510 : 0 : submatchlet = l->data;
1511 [ # # ]: 0 : if (matchlet_match (submatchlet, root))
1512 : 0 : return TRUE;
1513 : : }
1514 : :
1515 : 0 : return FALSE;
1516 : : }
1517 : :
1518 : : static void
1519 : 48 : match_match (TreeMatch *match,
1520 : : GFile *root,
1521 : : GPtrArray *types)
1522 : : {
1523 : : GList *l;
1524 : :
1525 [ + + ]: 144 : for (l = match->matches; l; l = l->next)
1526 : : {
1527 : 99 : TreeMatchlet *matchlet = l->data;
1528 [ + + ]: 99 : if (matchlet_match (matchlet, root))
1529 : : {
1530 : 6 : g_ptr_array_add (types, g_strdup (match->contenttype));
1531 : 3 : break;
1532 : : }
1533 : : }
1534 : 48 : }
1535 : :
1536 : : /**
1537 : : * g_content_type_guess_for_tree:
1538 : : * @root: the root of the tree to guess a type for
1539 : : *
1540 : : * Tries to guess the type of the tree with root @root, by
1541 : : * looking at the files it contains. The result is an array
1542 : : * of content types, with the best guess coming first.
1543 : : *
1544 : : * The types returned all have the form x-content/foo, e.g.
1545 : : * x-content/audio-cdda (for audio CDs) or x-content/image-dcf
1546 : : * (for a camera memory card). See the
1547 : : * [shared-mime-info](http://www.freedesktop.org/wiki/Specifications/shared-mime-info-spec)
1548 : : * specification for more on x-content types.
1549 : : *
1550 : : * This function is useful in the implementation of
1551 : : * g_mount_guess_content_type().
1552 : : *
1553 : : * Returns: (transfer full) (array zero-terminated=1): an %NULL-terminated
1554 : : * array of zero or more content types. Free with g_strfreev()
1555 : : *
1556 : : * Since: 2.18
1557 : : */
1558 : : gchar **
1559 : 4 : g_content_type_guess_for_tree (GFile *root)
1560 : : {
1561 : : GPtrArray *types;
1562 : : GList *l;
1563 : :
1564 : 4 : types = g_ptr_array_new ();
1565 : :
1566 : 4 : G_LOCK (gio_treemagic);
1567 : :
1568 : 4 : tree_magic_init ();
1569 [ + + ]: 52 : for (l = tree_matches; l; l = l->next)
1570 : : {
1571 : 48 : TreeMatch *match = l->data;
1572 : 48 : match_match (match, root, types);
1573 : : }
1574 : :
1575 : 4 : G_UNLOCK (gio_treemagic);
1576 : :
1577 : 4 : g_ptr_array_add (types, NULL);
1578 : :
1579 : 4 : return (gchar **)g_ptr_array_free (types, FALSE);
1580 : : }
|