1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
4 * Copyright (C) 1998 Miguel de Icaza
5 * Copyright (C) 1997 Paolo Molaro
6 * Copyright (C) 2000, 2001 Eazel, Inc.
9 * This file is part of the Gnome Library.
11 * The Gnome Library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Library General Public License as
13 * published by the Free Software Foundation; either version 2 of the
14 * License, or (at your option) any later version.
16 * The Gnome Library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Library General Public License for more details.
21 * You should have received a copy of the GNU Library General Public
22 * License along with the Gnome Library; see the file COPYING.LIB. If not,
23 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 * Boston, MA 02111-1307, USA.
28 #include "gnome-vfs-mime.h"
30 #include "gnome-vfs-mime-private.h"
31 #include "gnome-vfs-mime-sniff-buffer-private.h"
32 #include "gnome-vfs-mime-utils.h"
33 #include "gnome-vfs-module-shared.h"
34 #include "gnome-vfs-ops.h"
35 #include "gnome-vfs-result.h"
36 #include "gnome-vfs-uri.h"
43 static gboolean module_inited = FALSE;
45 static GHashTable *mime_extensions [2] = { NULL, NULL };
46 static GList *mime_regexs [2] = { NULL, NULL };
48 #define DEFAULT_DATE_TRACKER_INTERVAL 5 /* in milliseconds */
57 unsigned int valid : 1;
58 unsigned int system_dir : 1;
66 struct FileDateTracker {
72 /* These ones are used to automatically reload mime-types on demand */
73 static mime_dir_source_t gnome_mime_dir, user_mime_dir;
74 static FileDateTracker *mime_data_date_tracker;
76 #ifdef G_THREADS_ENABLED
78 /* We lock this mutex whenever we modify global state in this module. */
79 G_LOCK_DEFINE_STATIC (mime_mutex);
81 #endif /* G_LOCK_DEFINE_STATIC */
85 get_priority (char *def, int *priority)
94 } else if (*def == '2') {
100 while (*def && *def == ':')
107 list_find_type (gconstpointer value, gconstpointer type)
109 return g_ascii_strcasecmp((const char *) value, (const char *) type);
113 add_to_key (char *mime_type, char *def)
119 if (strncmp (def, "ext", 3) == 0){
123 def = get_priority (def, &priority);
124 s = p = g_strdup (def);
126 while ((ext = strtok_r (s, " \t\n\r,", &tokp)) != NULL) {
130 found = g_hash_table_lookup_extended (mime_extensions [priority], ext,
131 &orig_key, (gpointer *)&list);
137 if (!g_list_find_custom (list, mime_type, list_find_type)) {
138 list = g_list_prepend (list, g_strdup (mime_type));
139 g_hash_table_insert (mime_extensions [priority],
140 found ? orig_key : g_strdup (ext), list);
147 if (strncmp (def, "regex", 5) == 0) {
150 def = get_priority (def, &priority);
152 while (g_ascii_isspace (*def)) {
160 /* This was g_new instead of g_new0, but there seems
161 * to be a bug in the Solaris? version of regcomp that
162 * requires an initialized regex or it will crash.
164 mp = g_new0 (RegexMimePair, 1);
165 if (regcomp (&mp->regex, def, REG_EXTENDED | REG_NOSUB)) {
169 mp->mime_type = g_strdup (mime_type);
171 mime_regexs [priority] = g_list_prepend (mime_regexs [priority], mp);
176 mime_fill_from_file (const char *filename)
182 g_assert (filename != NULL);
184 _gnome_vfs_file_date_tracker_start_tracking_file (mime_data_date_tracker, filename);
185 file = fopen (filename, "r");
192 while (fgets (buf, sizeof (buf), file) != NULL) {
195 if (buf [0] == '#') {
199 /* Trim trailing spaces */
200 for (p = buf + strlen (buf) - 1; p >= buf; p--) {
201 if (!g_ascii_isspace (*p)) {
207 if (buf [0] == '\0') {
211 if (buf [0] == '\t' || buf [0] == ' '){
215 while (g_ascii_isspace (*p))
221 add_to_key (current_key, p);
224 g_free (current_key);
226 current_key = g_strdup (buf);
227 if (current_key [strlen (current_key)-1] == ':')
228 current_key [strlen (current_key)-1] = 0;
232 g_free (current_key);
238 mime_load (mime_dir_source_t *source)
242 const int extlen = sizeof (".mime") - 1;
246 g_return_if_fail (source != NULL);
247 g_return_if_fail (source->dirname != NULL);
249 source->valid = (stat (source->dirname, &s) != -1);
251 if (source->system_dir) {
252 filename = g_strconcat (source->dirname, "/gnome-vfs.mime", NULL);
253 mime_fill_from_file (filename);
257 dir = opendir (source->dirname);
259 while (dir != NULL) {
262 dent = readdir (dir);
267 len = strlen (dent->d_name);
273 if (strcmp (dent->d_name + len - extlen, ".mime") != 0) {
277 if (source->system_dir && strcmp (dent->d_name, "gnome-vfs.mime") == 0) {
281 if (source->system_dir && strcmp (dent->d_name, "gnome.mime") == 0) {
282 /* Ignore the obsolete "official" one so it doesn't override
283 * the new official one.
288 if (!source->system_dir && strcmp (dent->d_name, "user.mime") == 0) {
292 filename = g_strconcat (source->dirname, "/", dent->d_name, NULL);
294 mime_fill_from_file (filename);
300 if (!source->system_dir) {
301 filename = g_strconcat (source->dirname, "/user.mime", NULL);
302 mime_fill_from_file (filename);
306 _gnome_vfs_file_date_tracker_start_tracking_file (mime_data_date_tracker, source->dirname);
310 remove_one_mime_hash_entry (gpointer key, gpointer value, gpointer user_data)
313 g_list_foreach (value, (GFunc) g_free, NULL);
320 mime_extensions_empty (void)
324 for (i = 0; i < 2; i++) {
325 if (mime_extensions [i] != NULL) {
326 g_hash_table_foreach_remove (mime_extensions [i],
327 remove_one_mime_hash_entry, NULL);
330 for (p = mime_regexs [i]; p != NULL; p = p->next){
331 RegexMimePair *mp = p->data;
333 g_free (mp->mime_type);
334 regfree (&mp->regex);
337 g_list_free (mime_regexs [i]);
338 mime_regexs [i] = NULL;
345 if (!_gnome_vfs_file_date_tracker_date_has_changed (mime_data_date_tracker)) {
349 mime_extensions_empty ();
351 mime_load (&gnome_mime_dir);
352 mime_load (&user_mime_dir);
358 mime_extensions [0] = g_hash_table_new (g_str_hash, g_str_equal);
359 mime_extensions [1] = g_hash_table_new (g_str_hash, g_str_equal);
361 mime_data_date_tracker = _gnome_vfs_file_date_tracker_new ();
363 gnome_mime_dir.dirname = g_strdup (DATADIR "/mime-info");
364 gnome_mime_dir.system_dir = TRUE;
366 user_mime_dir.dirname = g_strconcat (g_get_home_dir (), "/.gnome/mime-info", NULL);
367 user_mime_dir.system_dir = FALSE;
369 mime_load (&gnome_mime_dir);
370 mime_load (&user_mime_dir);
372 module_inited = TRUE;
376 * gnome_vfs_mime_shutdown:
378 * Unload the MIME database from memory.
382 gnome_vfs_mime_shutdown (void)
384 _gnome_vfs_mime_info_shutdown ();
385 _gnome_vfs_mime_clear_magic_table ();
390 mime_extensions_empty ();
392 g_hash_table_destroy (mime_extensions[0]);
393 g_hash_table_destroy (mime_extensions[1]);
395 _gnome_vfs_file_date_tracker_free (mime_data_date_tracker);
397 g_free (gnome_mime_dir.dirname);
398 g_free (user_mime_dir.dirname);
402 * gnome_vfs_mime_type_from_name_or_default:
403 * @filename: A filename (the file does not necesarily exist).
404 * @defaultv: A default value to be returned if no match is found
406 * This routine tries to determine the mime-type of the filename
407 * only by looking at the filename from the GNOME database of mime-types.
409 * Returns the mime-type of the @filename. If no value could be
410 * determined, it will return @defaultv.
413 gnome_vfs_mime_type_from_name_or_default (const char *filename, const char *defaultv)
418 const char *result = defaultv;
420 if (filename == NULL) {
426 ext = strrchr (filename, '.');
431 if (!module_inited) {
437 for (priority = 1; priority >= 0; priority--){
443 list = g_hash_table_lookup (mime_extensions [priority], ext);
445 list = g_list_first( list );
446 result = (const char *) list->data;
450 /* Search for UPPER case extension */
451 upext = g_ascii_strup (ext, -1);
452 list = g_hash_table_lookup (mime_extensions [priority], upext);
455 list = g_list_first (list);
456 result = (const char *) list->data;
460 /* Final check for lower case */
461 upext = g_ascii_strdown (ext, -1);
462 list = g_hash_table_lookup (mime_extensions [priority], upext);
465 list = g_list_first (list);
466 result = (const char *) list->data;
471 for (l = mime_regexs [priority]; l; l = l->next){
472 RegexMimePair *mp = l->data;
474 if (regexec (&mp->regex, filename, 0, 0, 0) == 0) {
475 result = mp->mime_type;
476 G_UNLOCK (mime_mutex);
482 G_UNLOCK (mime_mutex);
487 * gnome_vfs_mime_type_from_name:
488 * @filename: A filename (the file does not necessarily exist).
490 * Determined the mime type for @filename.
492 * Returns the mime-type for this filename.
495 gnome_vfs_mime_type_from_name (const gchar * filename)
497 return gnome_vfs_mime_type_from_name_or_default (filename, GNOME_VFS_MIME_TYPE_UNKNOWN);
501 gnome_vfs_get_mime_type_from_uri_internal (GnomeVFSURI *uri)
504 const char *mime_type;
506 /* Return a mime type based on the file extension or NULL if no match. */
507 base_name = gnome_vfs_uri_extract_short_path_name (uri);
508 if (base_name == NULL) {
512 mime_type = gnome_vfs_mime_type_from_name_or_default (base_name, NULL);
518 _gnome_vfs_get_mime_type_internal (GnomeVFSMimeSniffBuffer *buffer, const char *file_name)
524 if (buffer != NULL) {
525 result = _gnome_vfs_mime_get_type_from_magic_table (buffer);
527 if (result != NULL) {
528 if (strcmp (result, "application/x-gzip") == 0) {
530 /* So many file types come compressed by gzip
531 * that extensions are more reliable than magic
532 * typing. If the file has a suffix, then use
533 * the type from the suffix.
535 * FIXME bugzilla.gnome.org 46867:
536 * Allow specific mime types to override
539 if (file_name != NULL) {
540 result = gnome_vfs_mime_type_from_name_or_default (file_name, NULL);
543 if (result != NULL) {
546 /* Didn't find an extension match,
548 return "application/x-gzip";
553 if (result == NULL) {
554 if (_gnome_vfs_sniff_buffer_looks_like_text (buffer)) {
555 /* Text file -- treat extensions as a more
556 * accurate source of type information.
558 if (file_name != NULL) {
559 result = gnome_vfs_mime_type_from_name_or_default (file_name, NULL);
562 if (result != NULL) {
566 /* Didn't find an extension match, assume plain text. */
569 } else if (_gnome_vfs_sniff_buffer_looks_like_mp3 (buffer)) {
575 if (result == NULL && file_name != NULL) {
576 /* No type recognized -- fall back on extensions. */
577 result = gnome_vfs_mime_type_from_name_or_default (file_name, NULL);
580 if (result == NULL) {
581 result = GNOME_VFS_MIME_TYPE_UNKNOWN;
588 * gnome_vfs_get_mime_type_common:
589 * @uri: a real file or a non-existent uri.
591 * Tries to guess the mime type of the file represented by @uir.
592 * Favors using the file data to the @uri extension.
593 * Handles passing @uri of a non-existent file by falling back
594 * on returning a type based on the extension.
596 * FIXME: This function will not necessarily return the same mime type as doing a
597 * get file info on the text uri.
599 * Returns: the mime-type for this uri.
603 gnome_vfs_get_mime_type_common (GnomeVFSURI *uri)
607 GnomeVFSMimeSniffBuffer *buffer;
608 GnomeVFSHandle *handle;
609 GnomeVFSResult error;
611 /* Check for special stat-defined file types first. */
612 result = gnome_vfs_get_special_mime_type (uri);
613 if (result != NULL) {
617 error = gnome_vfs_open_uri (&handle, uri, GNOME_VFS_OPEN_READ);
619 if (error != GNOME_VFS_OK) {
620 /* file may not exist, return type based on name only */
621 return gnome_vfs_get_mime_type_from_uri_internal (uri);
624 buffer = _gnome_vfs_mime_sniff_buffer_new_from_handle (handle);
626 base_name = gnome_vfs_uri_extract_short_path_name (uri);
628 result = _gnome_vfs_get_mime_type_internal (buffer, base_name);
631 gnome_vfs_mime_sniff_buffer_free (buffer);
632 gnome_vfs_close (handle);
637 static GnomeVFSResult
638 file_seek_binder (gpointer context, GnomeVFSSeekPosition whence,
639 GnomeVFSFileOffset offset)
641 FILE *file = (FILE *)context;
643 result = fseek (file, offset, whence);
645 return gnome_vfs_result_from_errno ();
650 static GnomeVFSResult
651 file_read_binder (gpointer context, gpointer buffer,
652 GnomeVFSFileSize bytes, GnomeVFSFileSize *bytes_read)
654 FILE *file = (FILE *)context;
655 *bytes_read = fread (buffer, 1, bytes, file);
656 if (*bytes_read < 0) {
658 return gnome_vfs_result_from_errno ();
665 * gnome_vfs_get_file_mime_type:
666 * @path: a path of a file.
667 * @optional_stat_info: optional stat buffer.
668 * @suffix_only: whether or not to do a magic-based lookup.
670 * Tries to guess the mime type of the file represented by @path.
671 * If @suffix_only is false, uses the mime-magic based lookup first.
672 * Handles passing @path of a non-existent file by falling back
673 * on returning a type based on the extension.
675 * Returns: the mime-type for this path
678 gnome_vfs_get_file_mime_type (const char *path, const struct stat *optional_stat_info,
679 gboolean suffix_only)
682 GnomeVFSMimeSniffBuffer *buffer;
683 struct stat tmp_stat_buffer;
689 /* get the stat info if needed */
690 if (optional_stat_info == NULL && stat (path, &tmp_stat_buffer) == 0) {
691 optional_stat_info = &tmp_stat_buffer;
694 /* single out special file types */
695 if (optional_stat_info && !S_ISREG(optional_stat_info->st_mode)) {
696 if (S_ISDIR(optional_stat_info->st_mode)) {
697 return "x-directory/normal";
698 } else if (S_ISCHR(optional_stat_info->st_mode)) {
699 return "x-special/device-char";
700 } else if (S_ISBLK(optional_stat_info->st_mode)) {
701 return "x-special/device-block";
702 } else if (S_ISFIFO(optional_stat_info->st_mode)) {
703 return "x-special/fifo";
704 } else if (S_ISSOCK(optional_stat_info->st_mode)) {
705 return "x-special/socket";
707 /* unknown entry type, return generic file type */
708 return GNOME_VFS_MIME_TYPE_UNKNOWN;
713 file = fopen(path, "r");
717 buffer = _gnome_vfs_mime_sniff_buffer_new_generic
718 (file_seek_binder, file_read_binder, file);
720 result = _gnome_vfs_get_mime_type_internal (buffer, path);
721 gnome_vfs_mime_sniff_buffer_free (buffer);
724 result = _gnome_vfs_get_mime_type_internal (NULL, path);
728 g_assert (result != NULL);
733 * gnome_vfs_get_mime_type_from_uri:
736 * Tries to guess the mime type of the file @uri by
737 * checking the file name extension. Works on non-existent
740 * Returns the mime-type for this filename.
743 gnome_vfs_get_mime_type_from_uri (GnomeVFSURI *uri)
747 result = gnome_vfs_get_mime_type_from_uri_internal (uri);
748 if (result == NULL) {
749 /* no type, return generic file type */
750 result = GNOME_VFS_MIME_TYPE_UNKNOWN;
757 * gnome_vfs_get_mime_type_from_file_data:
760 * Tries to guess the mime type of the file @uri by
761 * checking the file data using the magic patterns. Does not handle text files properly
763 * Returns the mime-type for this filename.
766 gnome_vfs_get_mime_type_from_file_data (GnomeVFSURI *uri)
769 GnomeVFSMimeSniffBuffer *buffer;
770 GnomeVFSHandle *handle;
771 GnomeVFSResult error;
773 error = gnome_vfs_open_uri (&handle, uri, GNOME_VFS_OPEN_READ);
775 if (error != GNOME_VFS_OK) {
776 return GNOME_VFS_MIME_TYPE_UNKNOWN;
779 buffer = _gnome_vfs_mime_sniff_buffer_new_from_handle (handle);
780 result = _gnome_vfs_get_mime_type_internal (buffer, NULL);
781 gnome_vfs_mime_sniff_buffer_free (buffer);
782 gnome_vfs_close (handle);
788 * gnome_vfs_get_mime_type_for_data:
789 * @data: A pointer to data in memory.
790 * @data_size: Size of the data.
792 * Tries to guess the mime type of the data in @data
793 * using the magic patterns.
795 * Returns the mime-type for this filename.
798 gnome_vfs_get_mime_type_for_data (gconstpointer data, int data_size)
801 GnomeVFSMimeSniffBuffer *buffer;
803 buffer = gnome_vfs_mime_sniff_buffer_new_from_existing_data
805 result = _gnome_vfs_get_mime_type_internal (buffer, NULL);
807 gnome_vfs_mime_sniff_buffer_free (buffer);
813 gnome_vfs_mime_type_is_supertype (const char *mime_type)
817 if (mime_type == NULL) {
821 length = strlen (mime_type);
824 && mime_type[length - 2] == '/'
825 && mime_type[length - 1] == '*';
829 extract_prefix_add_suffix (const char *string, const char *separator, const char *suffix)
831 const char *separator_position;
835 separator_position = strstr (string, separator);
836 prefix_length = separator_position == NULL
838 : separator_position - string;
840 result = g_malloc (prefix_length + strlen (suffix) + 1);
842 strncpy (result, string, prefix_length);
843 result[prefix_length] = '\0';
845 strcat (result, suffix);
850 /* Returns the supertype for a mime type. Note that if called
851 * on a supertype it will return a copy of the supertype.
854 gnome_vfs_get_supertype_from_mime_type (const char *mime_type)
856 if (mime_type == NULL) {
859 return extract_prefix_add_suffix (mime_type, "/", "/*");
863 file_date_record_update_mtime (FileDateRecord *record)
866 record->mtime = (stat (record->file_path, &s) != -1) ? s.st_mtime : 0;
869 static FileDateRecord *
870 file_date_record_new (const char *file_path) {
871 FileDateRecord *record;
873 record = g_new0 (FileDateRecord, 1);
874 record->file_path = g_strdup (file_path);
876 file_date_record_update_mtime (record);
882 file_date_record_free (FileDateRecord *record)
884 g_free (record->file_path);
889 _gnome_vfs_file_date_tracker_new (void)
891 FileDateTracker *tracker;
893 tracker = g_new0 (FileDateTracker, 1);
894 tracker->check_interval = DEFAULT_DATE_TRACKER_INTERVAL;
895 tracker->records = g_hash_table_new (g_str_hash, g_str_equal);
901 release_key_and_value (gpointer key, gpointer value, gpointer user_data)
904 file_date_record_free (value);
910 _gnome_vfs_file_date_tracker_free (FileDateTracker *tracker)
912 g_hash_table_foreach_remove (tracker->records, release_key_and_value, NULL);
913 g_hash_table_destroy (tracker->records);
918 * Record the current mod date for a specified file, so that we can check
919 * later whether it has changed.
922 _gnome_vfs_file_date_tracker_start_tracking_file (FileDateTracker *tracker,
923 const char *local_file_path)
925 FileDateRecord *record;
927 record = g_hash_table_lookup (tracker->records, local_file_path);
928 if (record != NULL) {
929 file_date_record_update_mtime (record);
931 g_hash_table_insert (tracker->records,
932 g_strdup (local_file_path),
933 file_date_record_new (local_file_path));
938 check_and_update_one (gpointer key, gpointer value, gpointer user_data)
940 FileDateRecord *record;
941 gboolean *return_has_changed;
944 g_assert (key != NULL);
945 g_assert (value != NULL);
946 g_assert (user_data != NULL);
948 record = (FileDateRecord *)value;
949 return_has_changed = (gboolean *)user_data;
951 if (stat (record->file_path, &s) != -1) {
952 if (s.st_mtime != record->mtime) {
953 record->mtime = s.st_mtime;
954 *return_has_changed = TRUE;
960 _gnome_vfs_file_date_tracker_date_has_changed (FileDateTracker *tracker)
963 gboolean any_date_changed;
967 /* Note that this might overflow once in a blue moon, but the
968 * only side-effect of that would be a slightly-early check
971 if (tracker->last_checked + tracker->check_interval >= now) {
975 any_date_changed = FALSE;
977 g_hash_table_foreach (tracker->records, check_and_update_one, &any_date_changed);
979 tracker->last_checked = now;
981 return any_date_changed;
988 * gnome_vfs_get_mime_type:
989 * @text_uri: URI of the file for which to get the mime type
991 * Determine the mime type of @text_uri. The mime type is determined
992 * in the same way as by gnome_vfs_get_file_info(). This is meant as
993 * a convenience function for times when you only want the mime type.
995 * Return value: The mime type, or NULL if there is an error reading
999 gnome_vfs_get_mime_type (const char *text_uri)
1001 GnomeVFSFileInfo *info;
1003 GnomeVFSResult result;
1005 info = gnome_vfs_file_info_new ();
1006 result = gnome_vfs_get_file_info (text_uri, info,
1007 GNOME_VFS_FILE_INFO_GET_MIME_TYPE |
1008 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
1009 if (info->mime_type == NULL || result != GNOME_VFS_OK) {
1012 mime_type = g_strdup (info->mime_type);
1014 gnome_vfs_file_info_unref (info);