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