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