Added. Added. Added. Added.
[platform/upstream/glib.git] / gio / gcontenttype.c
1 /* GIO - GLib Input, Output and Streaming Library
2  * 
3  * Copyright (C) 2006-2007 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Author: Alexander Larsson <alexl@redhat.com>
21  */
22
23 #include <config.h>
24 #include <sys/types.h>
25 #include <dirent.h>
26 #include <string.h>
27 #include <stdio.h>
28 #include "gcontenttypeprivate.h"
29 #include "glibintl.h"
30
31 #include "gioalias.h"
32
33 /**
34  * SECTION:gcontenttype
35  * @short_description: platform specific content typing
36  *
37  * A content type is a platform specific string that defines the type
38  * of a file. On unix it is a mime type, on win32 it is an extension string
39  * like ".doc", ".txt" or a percieved string like "audio". Such strings
40  * can be looked up in the registry at HKEY_CLASSES_ROOT.
41  **/
42
43 #ifdef G_OS_WIN32
44
45 #include <windows.h>
46
47 static char *
48 get_registry_classes_key (const char *subdir,
49                           const wchar_t *key_name)
50 {
51   wchar_t *wc_key;
52   HKEY reg_key = NULL;
53   DWORD key_type;
54   DWORD nbytes;
55   char *value_utf8;
56
57   value_utf8 = NULL;
58   
59   nbytes = 0;
60   wc_key = g_utf8_to_utf16 (subdir, -1, NULL, NULL, NULL);
61   if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0,
62                      KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS &&
63       RegQueryValueExW (reg_key, key_name, 0,
64                         &key_type, NULL, &nbytes) == ERROR_SUCCESS &&
65       key_type == REG_SZ)
66     {
67       wchar_t *wc_temp = g_new (wchar_t, (nbytes+1)/2 + 1);
68       RegQueryValueExW (reg_key, key_name, 0,
69                         &key_type, (LPBYTE) wc_temp, &nbytes);
70       wc_temp[nbytes/2] = '\0';
71       value_utf8 = g_utf16_to_utf8 (wc_temp, -1, NULL, NULL, NULL);
72       g_free (wc_temp);
73     }
74   g_free (wc_key);
75   
76   if (reg_key != NULL)
77     RegCloseKey (reg_key);
78
79   return value_utf8;
80 }
81
82 gboolean
83 g_content_type_equals (const char *type1,
84                        const char *type2)
85 {
86   char *progid1, *progid2;
87   gboolean res;
88   
89   g_return_val_if_fail (type1 != NULL, FALSE);
90   g_return_val_if_fail (type2 != NULL, FALSE);
91
92   if (g_ascii_strcasecmp (type1, type2) == 0)
93     return TRUE;
94
95   res = FALSE;
96   progid1 = get_registry_classes_key (type1, NULL);
97   progid2 = get_registry_classes_key (type2, NULL);
98   if (progid1 != NULL && progid2 != NULL &&
99       strcmp (progid1, progid2) == 0)
100     res = TRUE;
101   g_free (progid1);
102   g_free (progid2);
103   
104   return res;
105 }
106
107 gboolean
108 g_content_type_is_a (const char   *type,
109                      const char   *supertype)
110 {
111   gboolean res;
112   char *value_utf8;
113
114   g_return_val_if_fail (type != NULL, FALSE);
115   g_return_val_if_fail (supertype != NULL, FALSE);
116
117   if (g_content_type_equals (type, supertype))
118     return TRUE;
119
120   res = FALSE;
121   value_utf8 = get_registry_classes_key (type, L"PerceivedType");
122   if (value_utf8 && strcmp (value_utf8, supertype) == 0)
123     res = TRUE;
124   g_free (value_utf8);
125   
126   return res;
127 }
128
129 gboolean
130 g_content_type_is_unknown (const char *type)
131 {
132   g_return_val_if_fail (type != NULL, FALSE);
133
134   return strcmp ("*", type) == 0;
135 }
136
137 char *
138 g_content_type_get_description (const char *type)
139 {
140   char *progid;
141   char *description;
142
143   g_return_val_if_fail (type != NULL, NULL);
144
145   progid = get_registry_classes_key (type, NULL);
146   if (progid)
147     {
148       description = get_registry_classes_key (progid, NULL);
149       g_free (progid);
150
151       if (description)
152         return description;
153     }
154
155   if (g_content_type_is_unknown (type))
156     return g_strdup (_("Unknown type"));
157   return g_strdup_printf (_("%s filetype"), type);
158 }
159
160 char *
161 g_content_type_get_mime_type (const char   *type)
162 {
163   char *mime;
164
165   g_return_val_if_fail (type != NULL, NULL);
166
167   mime = get_registry_classes_key (type, L"Content Type");
168   if (mime)
169     return mime;
170   else if (g_content_type_is_unknown (type))
171     return g_strdup ("application/octet-stream");
172   else if (*type == '.')
173     return g_strdup_printf ("application/x-ext-%s", type+1);
174   /* TODO: Map "image" to "image/ *", etc? */
175
176   return g_strdup ("application/octet-stream");
177 }
178
179 GIcon *
180 g_content_type_get_icon (const char   *type)
181 {
182   g_return_val_if_fail (type != NULL, NULL);
183
184   /* TODO: How do we represent icons???
185      In the registry they are the default value of
186      HKEY_CLASSES_ROOT\<progid>\DefaultIcon with typical values like:
187      <type>: <value>
188      REG_EXPAND_SZ: %SystemRoot%\System32\Wscript.exe,3
189      REG_SZ: shimgvw.dll,3
190   */
191   return NULL;
192 }
193
194 gboolean
195 g_content_type_can_be_executable (const char   *type)
196 {
197   g_return_val_if_fail (type != NULL, FALSE);
198
199   if (strcmp (type, ".exe") == 0 ||
200       strcmp (type, ".com") == 0 ||
201       strcmp (type, ".bat") == 0)
202     return TRUE;
203   return FALSE;
204 }
205
206 static gboolean
207 looks_like_text (const guchar *data, gsize data_size)
208 {
209   gsize i;
210   guchar c;
211   for (i = 0; i < data_size; i++)
212     {
213       c = data[i];
214       if (g_ascii_iscntrl (c) && !g_ascii_isspace (c))
215         return FALSE;
216     }
217   return TRUE;
218 }
219
220 char *
221 g_content_type_guess (const char   *filename,
222                       const guchar *data,
223                       gsize         data_size,
224                       gboolean     *result_uncertain)
225 {
226   char *basename;
227   char *type;
228   char *dot;
229
230   type = NULL;
231
232   if (filename)
233     {
234       basename = g_path_get_basename (filename);
235       dot = strrchr (basename, '.');
236       if (dot)
237         type = g_strdup (dot);
238       g_free (basename);
239     }
240
241   if (type)
242     return type;
243
244   if (data && looks_like_text (data, data_size))
245     return g_strdup (".txt");
246
247   return g_strdup ("*");
248 }
249
250 GList *
251 g_content_types_get_registered (void)
252 {
253   DWORD index;
254   wchar_t keyname[256];
255   DWORD key_len;
256   char *key_utf8;
257   GList *types;
258
259   types = NULL;
260   index = 0;
261   key_len = 256;
262   while (RegEnumKeyExW(HKEY_CLASSES_ROOT,
263                        index,
264                        keyname,
265                        &key_len,
266                        NULL,
267                        NULL,
268                        NULL,
269                        NULL) == ERROR_SUCCESS)
270     {
271       key_utf8 = g_utf16_to_utf8 (keyname, -1, NULL, NULL, NULL);
272       if (key_utf8)
273         {
274           if (*key_utf8 == '.')
275             types = g_list_prepend (types, key_utf8);
276           else
277             g_free (key_utf8);
278         }
279       index++;
280       key_len = 256;
281     }
282   
283   return g_list_reverse (types);
284 }
285
286 #else /* !G_OS_WIN32 - Unix specific version */
287
288 #define XDG_PREFIX _gio_xdg
289 #include "xdgmime/xdgmime.h"
290
291 /* We lock this mutex whenever we modify global state in this module.  */
292 G_LOCK_DEFINE_STATIC (gio_xdgmime);
293
294 gsize
295 _g_unix_content_type_get_sniff_len (void)
296 {
297   gsize size;
298
299   G_LOCK (gio_xdgmime);
300   size = xdg_mime_get_max_buffer_extents ();
301   G_UNLOCK (gio_xdgmime);
302
303   return size;
304 }
305
306 char *
307 _g_unix_content_type_unalias (const char *type)
308 {
309   char *res;
310   
311   G_LOCK (gio_xdgmime);
312   res = g_strdup (xdg_mime_unalias_mime_type (type));
313   G_UNLOCK (gio_xdgmime);
314   
315   return res;
316 }
317
318 char **
319 _g_unix_content_type_get_parents (const char *type)
320 {
321   const char *umime;
322   const char **parents;
323   GPtrArray *array;
324   int i;
325
326   array = g_ptr_array_new ();
327   
328   G_LOCK (gio_xdgmime);
329   
330   umime = xdg_mime_unalias_mime_type (type);
331   g_ptr_array_add (array, g_strdup (umime));
332   
333   parents = xdg_mime_get_mime_parents (umime);
334   for (i = 0; parents && parents[i] != NULL; i++)
335     g_ptr_array_add (array, g_strdup (parents[i]));
336   
337   G_UNLOCK (gio_xdgmime);
338   
339   g_ptr_array_add (array, NULL);
340   
341   return (char **)g_ptr_array_free (array, FALSE);
342 }
343
344 /**
345  * g_content_type_equals:
346  * @type1: a content type string.
347  * @type2: a content type string.
348  *
349  * Compares two content types for equality.
350  *
351  * Returns: %TRUE if the two strings are identical or equivalent,
352  * %FALSE otherwise.
353  **/  
354 gboolean
355 g_content_type_equals (const char   *type1,
356                        const char   *type2)
357 {
358   gboolean res;
359   
360   g_return_val_if_fail (type1 != NULL, FALSE);
361   g_return_val_if_fail (type2 != NULL, FALSE);
362   
363   G_LOCK (gio_xdgmime);
364   res = xdg_mime_mime_type_equal (type1, type2);
365   G_UNLOCK (gio_xdgmime);
366         
367   return res;
368 }
369
370 /**
371  * g_content_type_is_a:
372  * @type: a content type string. 
373  * @supertype: a string.
374  *
375  * Determines if @type is a subset of @supertype.  
376  *
377  * Returns: %TRUE if @type is a kind of @supertype,
378  * %FALSE otherwise. 
379  **/  
380 gboolean
381 g_content_type_is_a (const char   *type,
382                      const char   *supertype)
383 {
384   gboolean res;
385     
386   g_return_val_if_fail (type != NULL, FALSE);
387   g_return_val_if_fail (supertype != NULL, FALSE);
388   
389   G_LOCK (gio_xdgmime);
390   res = xdg_mime_mime_type_subclass (type, supertype);
391   G_UNLOCK (gio_xdgmime);
392         
393   return res;
394 }
395
396 /**
397  * g_content_type_is_unknown:
398  * @type: a content type string. 
399  * 
400  * Checks if the content type is known by GIO.
401  * 
402  * Returns: %TRUE if the type is unknown.
403  **/  
404 gboolean
405 g_content_type_is_unknown (const char *type)
406 {
407   g_return_val_if_fail (type != NULL, FALSE);
408
409   return strcmp (XDG_MIME_TYPE_UNKNOWN, type) == 0;
410 }
411
412
413 typedef enum {
414   MIME_TAG_TYPE_OTHER,
415   MIME_TAG_TYPE_COMMENT
416 } MimeTagType;
417
418 typedef struct {
419   int current_type;
420   int current_lang_level;
421   int comment_lang_level;
422   char *comment;
423 } MimeParser;
424
425
426 static int
427 language_level (const char *lang)
428 {
429   const char * const *lang_list;
430   int i;
431   
432   /* The returned list is sorted from most desirable to least
433      desirable and always contains the default locale "C". */
434   lang_list = g_get_language_names ();
435   
436   for (i = 0; lang_list[i]; i++)
437     if (strcmp (lang_list[i], lang) == 0)
438       return 1000-i;
439   
440   return 0;
441 }
442
443 static void
444 mime_info_start_element (GMarkupParseContext *context,
445                          const gchar         *element_name,
446                          const gchar        **attribute_names,
447                          const gchar        **attribute_values,
448                          gpointer             user_data,
449                          GError             **error)
450 {
451   int i;
452   const char *lang;
453   MimeParser *parser = user_data;
454   
455   if (strcmp (element_name, "comment") == 0)
456     {
457       lang = "C";
458       for (i = 0; attribute_names[i]; i++)
459         if (strcmp (attribute_names[i], "xml:lang") == 0)
460           {
461             lang = attribute_values[i];
462             break;
463           }
464       
465       parser->current_lang_level = language_level (lang);
466       parser->current_type = MIME_TAG_TYPE_COMMENT;
467     }
468   else
469     parser->current_type = MIME_TAG_TYPE_OTHER;
470   
471 }
472
473 static void
474 mime_info_end_element (GMarkupParseContext *context,
475                        const gchar         *element_name,
476                        gpointer             user_data,
477                        GError             **error)
478 {
479   MimeParser *parser = user_data;
480   
481   parser->current_type = MIME_TAG_TYPE_OTHER;
482 }
483
484 static void
485 mime_info_text (GMarkupParseContext *context,
486                 const gchar         *text,
487                 gsize                text_len,  
488                 gpointer             user_data,
489                 GError             **error)
490 {
491   MimeParser *parser = user_data;
492
493   if (parser->current_type == MIME_TAG_TYPE_COMMENT &&
494       parser->current_lang_level > parser->comment_lang_level)
495     {
496       g_free (parser->comment);
497       parser->comment = g_strndup (text, text_len);
498       parser->comment_lang_level = parser->current_lang_level;
499     }
500 }
501
502 static char *
503 load_comment_for_mime_helper (const char *dir, const char *basename)
504 {
505   GMarkupParseContext *context;
506   char *filename, *data;
507   gsize len;
508   gboolean res;
509   MimeParser parse_data = {0};
510   GMarkupParser parser = {
511     mime_info_start_element,
512     mime_info_end_element,
513     mime_info_text
514   };
515
516   filename = g_build_filename (dir, "mime", basename, NULL);
517   
518   res = g_file_get_contents (filename,  &data,  &len,  NULL);
519   g_free (filename);
520   if (!res)
521     return NULL;
522   
523   context = g_markup_parse_context_new   (&parser, 0, &parse_data, NULL);
524   res = g_markup_parse_context_parse (context, data, len, NULL);
525   g_free (data);
526   g_markup_parse_context_free (context);
527   
528   if (!res)
529     return NULL;
530
531   return parse_data.comment;
532 }
533
534
535 static char *
536 load_comment_for_mime (const char *mimetype)
537 {
538   const char * const* dirs;
539   char *basename;
540   char *comment;
541   int i;
542
543   basename = g_strdup_printf ("%s.xml", mimetype);
544
545   comment = load_comment_for_mime_helper (g_get_user_data_dir (), basename);
546   if (comment)
547     {
548       g_free (basename);
549       return comment;
550     }
551   
552   dirs = g_get_system_data_dirs ();
553
554   for (i = 0; dirs[i] != NULL; i++)
555     {
556       comment = load_comment_for_mime_helper (dirs[i], basename);
557       if (comment)
558         {
559           g_free (basename);
560           return comment;
561         }
562     }
563   g_free (basename);
564   
565   return g_strdup_printf (_("%s type"), mimetype);
566 }
567
568 /**
569  * g_content_type_get_description:
570  * @type: a content type string. 
571  * 
572  * Gets the human readable description of the content type.
573  * 
574  * Returns: a short description of the content type @type. 
575  **/  
576 char *
577 g_content_type_get_description (const char *type)
578 {
579   static GHashTable *type_comment_cache = NULL;
580   char *comment;
581
582   g_return_val_if_fail (type != NULL, NULL);
583   
584   G_LOCK (gio_xdgmime);
585   if (type_comment_cache == NULL)
586     type_comment_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
587
588   comment = g_hash_table_lookup (type_comment_cache, type);
589   comment = g_strdup (comment);
590   G_UNLOCK (gio_xdgmime);
591   
592   if (comment != NULL)
593     return comment;
594
595   comment = load_comment_for_mime (type);
596   
597   G_LOCK (gio_xdgmime);
598   g_hash_table_insert (type_comment_cache,
599                        g_strdup (type),
600                        g_strdup (comment));
601   G_UNLOCK (gio_xdgmime);
602
603   return comment;
604 }
605
606 /**
607  * g_content_type_get_mime_type:
608  * @type: a content type string. 
609  * 
610  * Gets the mime-type for the content type.
611  * 
612  * Returns: the registered mime-type for the given @type.
613  **/  
614 char *
615 g_content_type_get_mime_type (const char   *type)
616 {
617   g_return_val_if_fail (type != NULL, NULL);
618
619   return g_strdup (type);
620 }
621
622 /**
623  * g_content_type_get_icon:
624  * @type: a content type string.
625  * 
626  * Gets the icon for a content type.
627  * 
628  * Returns: #GIcon corresponding to the content type.
629  **/  
630 GIcon *
631 g_content_type_get_icon (const char   *type)
632 {
633   g_return_val_if_fail (type != NULL, NULL);
634
635   /* TODO: Implement */
636   return NULL;
637 }
638
639 /**
640  * g_content_type_can_be_executable:
641  * @type: a content type string.
642  * 
643  * Checks if a content type can be executable. Note that for instance
644  * things like text files can be executables (i.e. scripts and batch files).
645  * 
646  * Returns: %TRUE if the file type corresponds to a type that
647  * can be executable, %FALSE otherwise. 
648  **/  
649 gboolean
650 g_content_type_can_be_executable (const char   *type)
651 {
652   g_return_val_if_fail (type != NULL, FALSE);
653
654   if (g_content_type_is_a (type, "application/x-executable")  ||
655       g_content_type_is_a (type, "text/plain"))
656     return TRUE;
657
658   return FALSE;
659 }
660
661 static gboolean
662 looks_like_text (const guchar *data, gsize data_size)
663 {
664   gsize i;
665   for (i = 0; i < data_size; i++)
666     {
667       if g_ascii_iscntrl (data[i])
668         return FALSE;
669     }
670   return TRUE;
671 }
672
673 /**
674  * g_content_type_guess:
675  * @filename: a string.
676  * @data: a stream of data.
677  * @data_size: the size of @data.
678  * @result_uncertain: a flag indicating the certainty of the 
679  * result.
680  * 
681  * Guesses the content type based on example data. If the function is 
682  * uncertain, @result_uncertain will be set to %TRUE.
683  * 
684  * Returns: a string indicating a guessed content type for the 
685  * given data. 
686  **/  
687 char *
688 g_content_type_guess (const char   *filename,
689                       const guchar *data,
690                       gsize         data_size,
691                       gboolean     *result_uncertain)
692 {
693   char *basename;
694   const char *name_mimetypes[10], *sniffed_mimetype;
695   char *mimetype;
696   int i;
697   int n_name_mimetypes;
698   int sniffed_prio;
699
700   sniffed_prio = 0;
701   n_name_mimetypes = 0;
702   sniffed_mimetype = XDG_MIME_TYPE_UNKNOWN;
703
704   if (result_uncertain)
705     *result_uncertain = FALSE;
706   
707   G_LOCK (gio_xdgmime);
708   
709   if (filename)
710     {
711       basename = g_path_get_basename (filename);
712       n_name_mimetypes = xdg_mime_get_mime_types_from_file_name (basename, name_mimetypes, 10);
713       g_free (basename);
714     }
715
716   /* Got an extension match, and no conflicts. This is it. */
717   if (n_name_mimetypes == 1)
718     {
719       G_UNLOCK (gio_xdgmime);
720       return g_strdup (name_mimetypes[0]);
721     }
722   
723   if (data)
724     {
725       sniffed_mimetype = xdg_mime_get_mime_type_for_data (data, data_size, &sniffed_prio);
726       if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN &&
727           data &&
728           looks_like_text (data, data_size))
729         sniffed_mimetype = "text/plain";
730     }
731   
732   if (n_name_mimetypes == 0)
733     {
734       if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN &&
735           result_uncertain)
736         *result_uncertain = TRUE;
737       
738       mimetype = g_strdup (sniffed_mimetype);
739     }
740   else
741     {
742       mimetype = NULL;
743       if (sniffed_mimetype != XDG_MIME_TYPE_UNKNOWN)
744         {
745           if (sniffed_prio >= 80) /* High priority sniffing match, use that */
746             mimetype = g_strdup (sniffed_mimetype);
747           else
748             {
749               /* There are conflicts between the name matches and we have a sniffed
750                  type, use that as a tie breaker. */
751               
752               for (i = 0; i < n_name_mimetypes; i++)
753                 {
754                   if ( xdg_mime_mime_type_subclass (name_mimetypes[i], sniffed_mimetype))
755                     {
756                       /* This nametype match is derived from (or the same as) the sniffed type).
757                          This is probably it. */
758                       mimetype = g_strdup (name_mimetypes[i]);
759                       break;
760                     }
761                 }
762             }
763         }
764       
765       if (mimetype == NULL)
766         {
767           /* Conflicts, and sniffed type was no help or not there. guess on the first one */
768           mimetype = g_strdup (name_mimetypes[0]);
769           if (result_uncertain)
770             *result_uncertain = TRUE;
771         }
772     }
773   
774   G_UNLOCK (gio_xdgmime);
775
776   return mimetype;
777 }
778
779 static gboolean
780 foreach_mimetype (gpointer  key,
781                   gpointer  value,
782                   gpointer  user_data)
783 {
784   GList **l = user_data;
785
786   *l = g_list_prepend (*l, (char *)key);
787   return TRUE;
788 }
789
790 static void
791 enumerate_mimetypes_subdir (const char *dir, const char *prefix, GHashTable *mimetypes)
792 {
793   DIR *d;
794   struct dirent *ent;
795   char *mimetype;
796
797   d = opendir (dir);
798   if (d)
799     {
800       while ((ent = readdir (d)) != NULL)
801         {
802           if (g_str_has_suffix (ent->d_name, ".xml"))
803             {
804               mimetype = g_strdup_printf ("%s/%.*s", prefix, (int) strlen (ent->d_name) - 4, ent->d_name);
805               g_hash_table_insert (mimetypes, mimetype, NULL);
806             }
807         }
808       closedir (d);
809     }
810 }
811
812 static void
813 enumerate_mimetypes_dir (const char *dir, GHashTable *mimetypes)
814 {
815   DIR *d;
816   struct dirent *ent;
817   char *mimedir;
818   char *name;
819
820   mimedir = g_build_filename (dir, "mime", NULL);
821   
822   d = opendir (mimedir);
823   if (d)
824     {
825       while ((ent = readdir (d)) != NULL)
826         {
827           if (strcmp (ent->d_name, "packages") != 0)
828             {
829               name = g_build_filename (mimedir, ent->d_name, NULL);
830               if (g_file_test (name, G_FILE_TEST_IS_DIR))
831                 enumerate_mimetypes_subdir (name, ent->d_name, mimetypes);
832               g_free (name);
833             }
834         }
835       closedir (d);
836     }
837   
838   g_free (mimedir);
839 }
840
841 /**
842  * g_content_types_get_registered:
843  * 
844  * Gets a list of strings containing the registered content types on 
845  * the system.
846  * 
847  * Returns: #GList of the registered content types.
848  **/  
849 GList *
850 g_content_types_get_registered (void)
851 {
852   const char * const* dirs;
853   GHashTable *mimetypes;
854   int i;
855   GList *l;
856
857   mimetypes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
858
859   enumerate_mimetypes_dir (g_get_user_data_dir (), mimetypes);
860   dirs = g_get_system_data_dirs ();
861
862   for (i = 0; dirs[i] != NULL; i++)
863     enumerate_mimetypes_dir (dirs[i], mimetypes);
864
865   l = NULL;
866   g_hash_table_foreach_steal (mimetypes, foreach_mimetype, &l);
867   g_hash_table_destroy (mimetypes);
868
869   return l;
870 }
871
872 #endif /* Unix version */
873
874 #define __G_CONTENT_TYPE_C__
875 #include "gioaliasdef.c"