Imported Upstream version 2.66.6
[platform/upstream/glib.git] / gio / gosxcontenttype.m
1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright (C) 2014 Patrick Griffis
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.1 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, see <http://www.gnu.org/licenses/>.
17  *
18  */
19
20 #include "config.h"
21
22 #include "gcontenttype.h"
23 #include "gicon.h"
24 #include "gthemedicon.h"
25
26 #include <CoreServices/CoreServices.h>
27
28 #define XDG_PREFIX _gio_xdg
29 #include "xdgmime/xdgmime.h"
30
31 /* We lock this mutex whenever we modify global state in this module.  */
32 G_LOCK_DEFINE_STATIC (gio_xdgmime);
33
34
35 /*< internal >
36  * create_cfstring_from_cstr:
37  * @cstr: a #gchar
38  *
39  * Converts a cstr to a utf8 cfstring
40  * It must be CFReleased()'d.
41  *
42  */
43 static CFStringRef
44 create_cfstring_from_cstr (const gchar *cstr)
45 {
46   return CFStringCreateWithCString (NULL, cstr, kCFStringEncodingUTF8);
47 }
48
49 /*< internal >
50  * create_cstr_from_cfstring:
51  * @str: a #CFStringRef
52  *
53  * Converts a cfstring to a utf8 cstring.
54  * The incoming cfstring is released for you.
55  * The returned string must be g_free()'d.
56  *
57  */
58 static gchar *
59 create_cstr_from_cfstring (CFStringRef str)
60 {
61   g_return_val_if_fail (str != NULL, NULL);
62
63   CFIndex length = CFStringGetLength (str);
64   CFIndex maxlen = CFStringGetMaximumSizeForEncoding (length, kCFStringEncodingUTF8);
65   gchar *buffer = g_malloc (maxlen + 1);
66   Boolean success = CFStringGetCString (str, (char *) buffer, maxlen,
67                                         kCFStringEncodingUTF8);
68   CFRelease (str);
69   if (success)
70     return buffer;
71   else
72     {
73       g_free (buffer);
74       return NULL;
75     }
76 }
77
78 /*< internal >
79  * create_cstr_from_cfstring_with_fallback:
80  * @str: a #CFStringRef
81  * @fallback: a #gchar
82  *
83  * Tries to convert a cfstring to a utf8 cstring.
84  * If @str is NULL or conversion fails @fallback is returned.
85  * The incoming cfstring is released for you.
86  * The returned string must be g_free()'d.
87  *
88  */
89 static gchar *
90 create_cstr_from_cfstring_with_fallback (CFStringRef  str,
91                                          const gchar *fallback)
92 {
93   gchar *cstr = NULL;
94
95   if (str)
96     cstr = create_cstr_from_cfstring (str);
97   if (!cstr)
98     return g_strdup (fallback);
99
100   return cstr;
101 }
102
103 /*< private >*/
104 void
105 g_content_type_set_mime_dirs (const gchar * const *dirs)
106 {
107   /* noop on macOS */
108 }
109
110 /*< private >*/
111 const gchar * const *
112 g_content_type_get_mime_dirs (void)
113 {
114   const gchar * const *mime_dirs = { NULL };
115   return mime_dirs;
116 }
117
118 gboolean
119 g_content_type_equals (const gchar *type1,
120                        const gchar *type2)
121 {
122   CFStringRef str1, str2;
123   gboolean ret;
124
125   g_return_val_if_fail (type1 != NULL, FALSE);
126   g_return_val_if_fail (type2 != NULL, FALSE);
127
128   if (g_ascii_strcasecmp (type1, type2) == 0)
129     return TRUE;
130
131   str1 = create_cfstring_from_cstr (type1);
132   str2 = create_cfstring_from_cstr (type2);
133
134   ret = UTTypeEqual (str1, str2);
135
136   CFRelease (str1);
137   CFRelease (str2);
138
139   return ret;
140 }
141
142 gboolean
143 g_content_type_is_a (const gchar *ctype,
144                      const gchar *csupertype)
145 {
146   CFStringRef type, supertype;
147   gboolean ret;
148
149   g_return_val_if_fail (ctype != NULL, FALSE);
150   g_return_val_if_fail (csupertype != NULL, FALSE);
151
152   type = create_cfstring_from_cstr (ctype);
153   supertype = create_cfstring_from_cstr (csupertype);
154
155   ret = UTTypeConformsTo (type, supertype);
156
157   CFRelease (type);
158   CFRelease (supertype);
159
160   return ret;
161 }
162
163 gboolean
164 g_content_type_is_mime_type (const gchar *type,
165                              const gchar *mime_type)
166 {
167   gchar *content_type;
168   gboolean ret;
169
170   g_return_val_if_fail (type != NULL, FALSE);
171   g_return_val_if_fail (mime_type != NULL, FALSE);
172
173   content_type = g_content_type_from_mime_type (mime_type);
174   ret = g_content_type_is_a (type, content_type);
175   g_free (content_type);
176
177   return ret;
178 }
179
180 gboolean
181 g_content_type_is_unknown (const gchar *type)
182 {
183   g_return_val_if_fail (type != NULL, FALSE);
184
185   /* Should dynamic types be considered "unknown"? */
186   if (g_str_has_prefix (type, "dyn."))
187     return TRUE;
188   /* application/octet-stream */
189   else if (g_strcmp0 (type, "public.data") == 0)
190     return TRUE;
191
192   return FALSE;
193 }
194
195 gchar *
196 g_content_type_get_description (const gchar *type)
197 {
198   CFStringRef str;
199   CFStringRef desc_str;
200
201   g_return_val_if_fail (type != NULL, NULL);
202
203   str = create_cfstring_from_cstr (type);
204   desc_str = UTTypeCopyDescription (str);
205
206   CFRelease (str);
207   return create_cstr_from_cfstring_with_fallback (desc_str, "unknown");
208 }
209
210 /* <internal>
211  * _get_generic_icon_name_from_mime_type
212  *
213  * This function produces a generic icon name from a @mime_type.
214  * If no generic icon name is found in the xdg mime database, the
215  * generic icon name is constructed.
216  *
217  * Background:
218  * generic-icon elements specify the icon to use as a generic icon for this
219  * particular mime-type, given by the name attribute. This is used if there
220  * is no specific icon (see icon for how these are found). These are used
221  * for categories of similar types (like spreadsheets or archives) that can
222  * use a common icon. The Icon Naming Specification lists a set of such
223  * icon names. If this element is not specified then the mimetype is used
224  * to generate the generic icon by using the top-level media type
225  * (e.g. "video" in "video/ogg") and appending "-x-generic"
226  * (i.e. "video-x-generic" in the previous example).
227  *
228  * From: https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-0.18.html
229  */
230
231 static gchar *
232 _get_generic_icon_name_from_mime_type (const gchar *mime_type)
233 {
234   const gchar *xdg_icon_name;
235   gchar *icon_name;
236
237   G_LOCK (gio_xdgmime);
238   xdg_icon_name = xdg_mime_get_generic_icon (mime_type);
239   G_UNLOCK (gio_xdgmime);
240
241   if (xdg_icon_name == NULL)
242     {
243       const char *p;
244       const char *suffix = "-x-generic";
245       gsize prefix_len;
246
247       p = strchr (mime_type, '/');
248       if (p == NULL)
249         prefix_len = strlen (mime_type);
250       else
251         prefix_len = p - mime_type;
252
253       icon_name = g_malloc (prefix_len + strlen (suffix) + 1);
254       memcpy (icon_name, mime_type, prefix_len);
255       memcpy (icon_name + prefix_len, suffix, strlen (suffix));
256       icon_name[prefix_len + strlen (suffix)] = 0;
257     }
258   else
259     {
260       icon_name = g_strdup (xdg_icon_name);
261     }
262
263   return icon_name;
264 }
265
266
267 static GIcon *
268 g_content_type_get_icon_internal (const gchar *uti,
269                                   gboolean     symbolic)
270 {
271   char *mimetype_icon;
272   char *mime_type;
273   char *generic_mimetype_icon = NULL;
274   char *q;
275   char *icon_names[6];
276   int n = 0;
277   GIcon *themed_icon;
278   const char  *xdg_icon;
279   int i;
280
281   g_return_val_if_fail (uti != NULL, NULL);
282
283   mime_type = g_content_type_get_mime_type (uti);
284
285   G_LOCK (gio_xdgmime);
286   xdg_icon = xdg_mime_get_icon (mime_type);
287   G_UNLOCK (gio_xdgmime);
288
289   if (xdg_icon)
290     icon_names[n++] = g_strdup (xdg_icon);
291
292   mimetype_icon = g_strdup (mime_type);
293   while ((q = strchr (mimetype_icon, '/')) != NULL)
294     *q = '-';
295
296   icon_names[n++] = mimetype_icon;
297
298   generic_mimetype_icon = _get_generic_icon_name_from_mime_type (mime_type);
299
300   if (generic_mimetype_icon)
301     icon_names[n++] = generic_mimetype_icon;
302
303   if (symbolic)
304     {
305       for (i = 0; i < n; i++)
306         {
307           icon_names[n + i] = icon_names[i];
308           icon_names[i] = g_strconcat (icon_names[i], "-symbolic", NULL);
309         }
310
311       n += n;
312     }
313
314   themed_icon = g_themed_icon_new_from_names (icon_names, n);
315  
316   for (i = 0; i < n; i++)
317     g_free (icon_names[i]);
318  
319   g_free(mime_type);
320  
321   return themed_icon;
322 }
323
324 GIcon *
325 g_content_type_get_icon (const gchar *type)
326 {
327   return g_content_type_get_icon_internal (type, FALSE);
328 }
329
330 GIcon *
331 g_content_type_get_symbolic_icon (const gchar *type)
332 {
333   return g_content_type_get_icon_internal (type, TRUE);
334 }
335
336 gchar *
337 g_content_type_get_generic_icon_name (const gchar *type)
338 {
339   return NULL;
340 }
341
342 gboolean
343 g_content_type_can_be_executable (const gchar *type)
344 {
345   CFStringRef uti;
346   gboolean ret = FALSE;
347
348   g_return_val_if_fail (type != NULL, FALSE);
349
350   uti = create_cfstring_from_cstr (type);
351
352   if (UTTypeConformsTo (uti, kUTTypeApplication))
353     ret = TRUE;
354   else if (UTTypeConformsTo (uti, CFSTR("public.executable")))
355     ret = TRUE;
356   else if (UTTypeConformsTo (uti, CFSTR("public.script")))
357     ret = TRUE;
358   /* Our tests assert that all text can be executable... */
359   else if (UTTypeConformsTo (uti, CFSTR("public.text")))
360       ret = TRUE;
361
362   CFRelease (uti);
363   return ret;
364 }
365
366 gchar *
367 g_content_type_from_mime_type (const gchar *mime_type)
368 {
369   CFStringRef mime_str;
370   CFStringRef uti_str;
371
372   g_return_val_if_fail (mime_type != NULL, NULL);
373
374   /* Their api does not handle globs but they are common. */
375   if (g_str_has_suffix (mime_type, "*"))
376     {
377       if (g_str_has_prefix (mime_type, "audio"))
378         return g_strdup ("public.audio");
379       if (g_str_has_prefix (mime_type, "image"))
380         return g_strdup ("public.image");
381       if (g_str_has_prefix (mime_type, "text"))
382         return g_strdup ("public.text");
383       if (g_str_has_prefix (mime_type, "video"))
384         return g_strdup ("public.movie");
385     }
386
387   /* Some exceptions are needed for gdk-pixbuf.
388    * This list is not exhaustive.
389    */
390   if (g_str_has_prefix (mime_type, "image"))
391     {
392       if (g_str_has_suffix (mime_type, "x-icns"))
393         return g_strdup ("com.apple.icns");
394       if (g_str_has_suffix (mime_type, "x-tga"))
395         return g_strdup ("com.truevision.tga-image");
396       if (g_str_has_suffix (mime_type, "x-ico"))
397         return g_strdup ("com.microsoft.ico ");
398     }
399
400   /* These are also not supported...
401    * Used in glocalfileinfo.c
402    */
403   if (g_str_has_prefix (mime_type, "inode"))
404     {
405       if (g_str_has_suffix (mime_type, "directory"))
406         return g_strdup ("public.folder");
407       if (g_str_has_suffix (mime_type, "symlink"))
408         return g_strdup ("public.symlink");
409     }
410
411   /* This is correct according to the Apple docs:
412      https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html
413   */
414   if (strcmp (mime_type, "text/plain") == 0)
415     return g_strdup ("public.text");
416
417   /* Non standard type */
418   if (strcmp (mime_type, "application/x-executable") == 0)
419     return g_strdup ("public.executable");
420
421   mime_str = create_cfstring_from_cstr (mime_type);
422   uti_str = UTTypeCreatePreferredIdentifierForTag (kUTTagClassMIMEType, mime_str, NULL);
423
424   CFRelease (mime_str);
425   return create_cstr_from_cfstring_with_fallback (uti_str, "public.data");
426 }
427
428 gchar *
429 g_content_type_get_mime_type (const gchar *type)
430 {
431   CFStringRef uti_str;
432   CFStringRef mime_str;
433
434   g_return_val_if_fail (type != NULL, NULL);
435
436   /* We must match the additions above
437    * so conversions back and forth work.
438    */
439   if (g_str_has_prefix (type, "public"))
440     {
441       if (g_str_has_suffix (type, ".image"))
442         return g_strdup ("image/*");
443       if (g_str_has_suffix (type, ".movie"))
444         return g_strdup ("video/*");
445       if (g_str_has_suffix (type, ".text"))
446         return g_strdup ("text/*");
447       if (g_str_has_suffix (type, ".audio"))
448         return g_strdup ("audio/*");
449       if (g_str_has_suffix (type, ".folder"))
450         return g_strdup ("inode/directory");
451       if (g_str_has_suffix (type, ".symlink"))
452         return g_strdup ("inode/symlink");
453       if (g_str_has_suffix (type, ".executable"))
454         return g_strdup ("application/x-executable");
455     }
456
457   uti_str = create_cfstring_from_cstr (type);
458   mime_str = UTTypeCopyPreferredTagWithClass(uti_str, kUTTagClassMIMEType);
459
460   CFRelease (uti_str);
461   return create_cstr_from_cfstring_with_fallback (mime_str, "application/octet-stream");
462 }
463
464 static gboolean
465 looks_like_text (const guchar *data,
466                  gsize         data_size)
467 {
468   gsize i;
469   guchar c;
470
471   for (i = 0; i < data_size; i++)
472     {
473       c = data[i];
474       if (g_ascii_iscntrl (c) && !g_ascii_isspace (c) && c != '\b')
475         return FALSE;
476     }
477   return TRUE;
478 }
479
480 gchar *
481 g_content_type_guess (const gchar  *filename,
482                       const guchar *data,
483                       gsize         data_size,
484                       gboolean     *result_uncertain)
485 {
486   CFStringRef uti = NULL;
487   gchar *cextension;
488   CFStringRef extension;
489   int uncertain = -1;
490
491   g_return_val_if_fail (data_size != (gsize) -1, NULL);
492
493   if (filename && *filename)
494     {
495       gchar *basename = g_path_get_basename (filename);
496       gchar *dirname = g_path_get_dirname (filename);
497       gsize i = strlen (filename);
498
499       if (filename[i - 1] == '/')
500         {
501           if (g_strcmp0 (dirname, "/Volumes") == 0)
502             {
503               uti = CFStringCreateCopy (NULL, kUTTypeVolume);
504             }
505           else if ((cextension = strrchr (basename, '.')) != NULL)
506             {
507               cextension++;
508               extension = create_cfstring_from_cstr (cextension);
509               uti = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension,
510                                                            extension, NULL);
511               CFRelease (extension);
512
513               if (CFStringHasPrefix (uti, CFSTR ("dyn.")))
514                 {
515                   CFRelease (uti);
516                   uti = CFStringCreateCopy (NULL, kUTTypeFolder);
517                   uncertain = TRUE;
518                 }
519             }
520           else
521             {
522               uti = CFStringCreateCopy (NULL, kUTTypeFolder);
523               uncertain = TRUE; /* Matches Unix backend */
524             }
525         }
526       else
527         {
528           /* GTK needs this... */
529           if (g_str_has_suffix (basename, ".ui"))
530             {
531               uti = CFStringCreateCopy (NULL, kUTTypeXML);
532             }
533           else if (g_str_has_suffix (basename, ".txt"))
534             {
535               uti = CFStringCreateCopy (NULL, CFSTR ("public.text"));
536             }
537           else if ((cextension = strrchr (basename, '.')) != NULL)
538             {
539               cextension++;
540               extension = create_cfstring_from_cstr (cextension);
541               uti = UTTypeCreatePreferredIdentifierForTag (kUTTagClassFilenameExtension,
542                                                            extension, NULL);
543               CFRelease (extension);
544             }
545           g_free (basename);
546           g_free (dirname);
547         }
548     }
549   if (data && (!filename || !uti ||
550                CFStringCompare (uti, CFSTR ("public.data"), 0) == kCFCompareEqualTo))
551     {
552       const char *sniffed_mimetype;
553       G_LOCK (gio_xdgmime);
554       sniffed_mimetype = xdg_mime_get_mime_type_for_data (data, data_size, NULL);
555       G_UNLOCK (gio_xdgmime);
556       if (sniffed_mimetype != XDG_MIME_TYPE_UNKNOWN)
557         {
558           gchar *uti_str = g_content_type_from_mime_type (sniffed_mimetype);
559           uti = create_cfstring_from_cstr (uti_str);
560           g_free (uti_str);
561         }
562       if (!uti && looks_like_text (data, data_size))
563         {
564           if (g_str_has_prefix ((const gchar*)data, "#!/"))
565             uti = CFStringCreateCopy (NULL, CFSTR ("public.script"));
566           else
567             uti = CFStringCreateCopy (NULL, CFSTR ("public.text"));
568         }
569     }
570
571   if (!uti)
572     {
573       /* Generic data type */
574       uti = CFStringCreateCopy (NULL, CFSTR ("public.data"));
575       if (result_uncertain)
576         *result_uncertain = TRUE;
577     }
578   else if (result_uncertain)
579     {
580       *result_uncertain = uncertain == -1 ? FALSE : uncertain;
581     }
582
583   return create_cstr_from_cfstring (uti);
584 }
585
586 GList *
587 g_content_types_get_registered (void)
588 {
589   /* TODO: UTTypeCreateAllIdentifiersForTag? */
590   return NULL;
591 }
592
593 gchar **
594 g_content_type_guess_for_tree (GFile *root)
595 {
596   return NULL;
597 }