glib/gio/gcontenttype.c
Alexander Larsson 9fdf70ba5f Don't ever sniff desktop files when the filename is known. In other words,
2009-02-20  Alexander Larsson  <alexl@redhat.com>

        * gcontenttype.c:
        (g_content_type_guess):
	Don't ever sniff desktop files when the filename is known.
	In other words, only allow desktop files with the .desktop extension
	and when the filename isn't known.
	This is a security precaution since desktop files can execute
	arbitrary code when launched and we don't want to allow them to
	try and hide as another type. There is no legit reason to not
	have the .desktop extension anyway.



svn path=/trunk/; revision=7892
2009-02-20 07:46:57 +00:00

1672 lines
39 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright (C) 2006-2007 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "gcontenttypeprivate.h"
#include "gthemedicon.h"
#include "gicon.h"
#include "gfile.h"
#include "gfileenumerator.h"
#include "gfileinfo.h"
#include "glibintl.h"
#include "gioalias.h"
/**
* SECTION:gcontenttype
* @short_description: Platform-specific content typing
* @include: gio/gio.h
*
* A content type is a platform specific string that defines the type
* of a file. On unix it is a mime type, on win32 it is an extension string
* like ".doc", ".txt" or a percieved string like "audio". Such strings
* can be looked up in the registry at HKEY_CLASSES_ROOT.
**/
#ifdef G_OS_WIN32
#include <windows.h>
static char *
get_registry_classes_key (const char *subdir,
const wchar_t *key_name)
{
wchar_t *wc_key;
HKEY reg_key = NULL;
DWORD key_type;
DWORD nbytes;
char *value_utf8;
value_utf8 = NULL;
nbytes = 0;
wc_key = g_utf8_to_utf16 (subdir, -1, NULL, NULL, NULL);
if (RegOpenKeyExW (HKEY_CLASSES_ROOT, wc_key, 0,
KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS &&
RegQueryValueExW (reg_key, key_name, 0,
&key_type, NULL, &nbytes) == ERROR_SUCCESS &&
(key_type == REG_SZ || key_type == REG_EXPAND_SZ))
{
wchar_t *wc_temp = g_new (wchar_t, (nbytes+1)/2 + 1);
RegQueryValueExW (reg_key, key_name, 0,
&key_type, (LPBYTE) wc_temp, &nbytes);
wc_temp[nbytes/2] = '\0';
if (key_type == REG_EXPAND_SZ)
{
wchar_t dummy[1];
int len = ExpandEnvironmentStringsW (wc_temp, dummy, 1);
if (len > 0)
{
wchar_t *wc_temp_expanded = g_new (wchar_t, len);
if (ExpandEnvironmentStringsW (wc_temp, wc_temp_expanded, len) == len)
value_utf8 = g_utf16_to_utf8 (wc_temp_expanded, -1, NULL, NULL, NULL);
g_free (wc_temp_expanded);
}
}
else
{
value_utf8 = g_utf16_to_utf8 (wc_temp, -1, NULL, NULL, NULL);
}
g_free (wc_temp);
}
g_free (wc_key);
if (reg_key != NULL)
RegCloseKey (reg_key);
return value_utf8;
}
gboolean
g_content_type_equals (const char *type1,
const char *type2)
{
char *progid1, *progid2;
gboolean res;
g_return_val_if_fail (type1 != NULL, FALSE);
g_return_val_if_fail (type2 != NULL, FALSE);
if (g_ascii_strcasecmp (type1, type2) == 0)
return TRUE;
res = FALSE;
progid1 = get_registry_classes_key (type1, NULL);
progid2 = get_registry_classes_key (type2, NULL);
if (progid1 != NULL && progid2 != NULL &&
strcmp (progid1, progid2) == 0)
res = TRUE;
g_free (progid1);
g_free (progid2);
return res;
}
gboolean
g_content_type_is_a (const char *type,
const char *supertype)
{
gboolean res;
char *value_utf8;
g_return_val_if_fail (type != NULL, FALSE);
g_return_val_if_fail (supertype != NULL, FALSE);
if (g_content_type_equals (type, supertype))
return TRUE;
res = FALSE;
value_utf8 = get_registry_classes_key (type, L"PerceivedType");
if (value_utf8 && strcmp (value_utf8, supertype) == 0)
res = TRUE;
g_free (value_utf8);
return res;
}
gboolean
g_content_type_is_unknown (const char *type)
{
g_return_val_if_fail (type != NULL, FALSE);
return strcmp ("*", type) == 0;
}
char *
g_content_type_get_description (const char *type)
{
char *progid;
char *description;
g_return_val_if_fail (type != NULL, NULL);
progid = get_registry_classes_key (type, NULL);
if (progid)
{
description = get_registry_classes_key (progid, NULL);
g_free (progid);
if (description)
return description;
}
if (g_content_type_is_unknown (type))
return g_strdup (_("Unknown type"));
return g_strdup_printf (_("%s filetype"), type);
}
char *
g_content_type_get_mime_type (const char *type)
{
char *mime;
g_return_val_if_fail (type != NULL, NULL);
mime = get_registry_classes_key (type, L"Content Type");
if (mime)
return mime;
else if (g_content_type_is_unknown (type))
return g_strdup ("application/octet-stream");
else if (*type == '.')
return g_strdup_printf ("application/x-ext-%s", type+1);
/* TODO: Map "image" to "image/ *", etc? */
return g_strdup ("application/octet-stream");
}
G_LOCK_DEFINE_STATIC (_type_icons);
static GHashTable *_type_icons = NULL;
GIcon *
g_content_type_get_icon (const char *type)
{
GIcon *themed_icon;
char *name = NULL;
g_return_val_if_fail (type != NULL, NULL);
/* In the Registry icons are the default value of
HKEY_CLASSES_ROOT\<progid>\DefaultIcon with typical values like:
<type>: <value>
REG_EXPAND_SZ: %SystemRoot%\System32\Wscript.exe,3
REG_SZ: shimgvw.dll,3
*/
G_LOCK (_type_icons);
if (!_type_icons)
_type_icons = g_hash_table_new (g_str_hash, g_str_equal);
name = g_hash_table_lookup (_type_icons, type);
if (!name && type[0] == '.')
{
/* double lookup by extension */
gchar *key = get_registry_classes_key (type, NULL);
if (!key)
key = g_strconcat (type+1, "file\\DefaultIcon", NULL);
else
{
gchar *key2 = g_strconcat (key, "\\DefaultIcon", NULL);
g_free (key);
key = key2;
}
name = get_registry_classes_key (key, NULL);
if (name && strcmp (name, "%1") == 0)
{
g_free (name);
name = NULL;
}
if (name)
g_hash_table_insert (_type_icons, g_strdup (type), g_strdup (name));
g_free (key);
}
/* icon-name similar to how it was with gtk-2-12 */
if (name)
{
themed_icon = g_themed_icon_new (name);
}
else
{
/* if not found an icon fall back to gtk-builtins */
name = strcmp (type, "inode/directory") == 0 ? "gtk-directory" :
g_content_type_can_be_executable (type) ? "gtk-execute" : "gtk-file";
g_hash_table_insert (_type_icons, g_strdup (type), g_strdup (name));
themed_icon = g_themed_icon_new_with_default_fallbacks (name);
}
G_UNLOCK (_type_icons);
return G_ICON (themed_icon);
}
gboolean
g_content_type_can_be_executable (const char *type)
{
g_return_val_if_fail (type != NULL, FALSE);
if (strcmp (type, ".exe") == 0 ||
strcmp (type, ".com") == 0 ||
strcmp (type, ".bat") == 0)
return TRUE;
/* TODO: Also look at PATHEXT, which lists the extensions for
* "scripts" in addition to those for true binary executables.
*
* (PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH for me
* right now, for instance). And in a sense, all associated file
* types are "executable" on Windows... You can just type foo.jpg as
* a command name in cmd.exe, and it will run the application
* associated with .jpg. Hard to say what this API actually means
* with "executable".
*/
return FALSE;
}
static gboolean
looks_like_text (const guchar *data,
gsize data_size)
{
gsize i;
guchar c;
for (i = 0; i < data_size; i++)
{
c = data[i];
if (g_ascii_iscntrl (c) && !g_ascii_isspace (c))
return FALSE;
}
return TRUE;
}
char *
g_content_type_from_mime_type (const char *mime_type)
{
char *key, *content_type;
g_return_val_if_fail (mime_type != NULL, NULL);
key = g_strconcat ("MIME\\DataBase\\Content Type\\", mime_type, NULL);
content_type = get_registry_classes_key (key, L"Extension");
g_free (key);
return content_type;
}
char *
g_content_type_guess (const char *filename,
const guchar *data,
gsize data_size,
gboolean *result_uncertain)
{
char *basename;
char *type;
char *dot;
type = NULL;
if (filename)
{
basename = g_path_get_basename (filename);
dot = strrchr (basename, '.');
if (dot)
type = g_strdup (dot);
g_free (basename);
}
if (type)
return type;
if (data && looks_like_text (data, data_size))
return g_strdup (".txt");
return g_strdup ("*");
}
GList *
g_content_types_get_registered (void)
{
DWORD index;
wchar_t keyname[256];
DWORD key_len;
char *key_utf8;
GList *types;
types = NULL;
index = 0;
key_len = 256;
while (RegEnumKeyExW(HKEY_CLASSES_ROOT,
index,
keyname,
&key_len,
NULL,
NULL,
NULL,
NULL) == ERROR_SUCCESS)
{
key_utf8 = g_utf16_to_utf8 (keyname, -1, NULL, NULL, NULL);
if (key_utf8)
{
if (*key_utf8 == '.')
types = g_list_prepend (types, key_utf8);
else
g_free (key_utf8);
}
index++;
key_len = 256;
}
return g_list_reverse (types);
}
char **
g_content_type_guess_for_tree (GFile *root)
{
/* FIXME: implement */
return NULL;
}
#else /* !G_OS_WIN32 - Unix specific version */
#include <dirent.h>
#define XDG_PREFIX _gio_xdg
#include "xdgmime/xdgmime.h"
/* We lock this mutex whenever we modify global state in this module. */
G_LOCK_DEFINE_STATIC (gio_xdgmime);
gsize
_g_unix_content_type_get_sniff_len (void)
{
gsize size;
G_LOCK (gio_xdgmime);
size = xdg_mime_get_max_buffer_extents ();
G_UNLOCK (gio_xdgmime);
return size;
}
char *
_g_unix_content_type_unalias (const char *type)
{
char *res;
G_LOCK (gio_xdgmime);
res = g_strdup (xdg_mime_unalias_mime_type (type));
G_UNLOCK (gio_xdgmime);
return res;
}
char **
_g_unix_content_type_get_parents (const char *type)
{
const char *umime;
char **parents;
GPtrArray *array;
int i;
array = g_ptr_array_new ();
G_LOCK (gio_xdgmime);
umime = xdg_mime_unalias_mime_type (type);
g_ptr_array_add (array, g_strdup (umime));
parents = xdg_mime_list_mime_parents (umime);
for (i = 0; parents && parents[i] != NULL; i++)
g_ptr_array_add (array, g_strdup (parents[i]));
free (parents);
G_UNLOCK (gio_xdgmime);
g_ptr_array_add (array, NULL);
return (char **)g_ptr_array_free (array, FALSE);
}
/**
* g_content_type_equals:
* @type1: a content type string.
* @type2: a content type string.
*
* Compares two content types for equality.
*
* Returns: %TRUE if the two strings are identical or equivalent,
* %FALSE otherwise.
**/
gboolean
g_content_type_equals (const char *type1,
const char *type2)
{
gboolean res;
g_return_val_if_fail (type1 != NULL, FALSE);
g_return_val_if_fail (type2 != NULL, FALSE);
G_LOCK (gio_xdgmime);
res = xdg_mime_mime_type_equal (type1, type2);
G_UNLOCK (gio_xdgmime);
return res;
}
/**
* g_content_type_is_a:
* @type: a content type string.
* @supertype: a string.
*
* Determines if @type is a subset of @supertype.
*
* Returns: %TRUE if @type is a kind of @supertype,
* %FALSE otherwise.
**/
gboolean
g_content_type_is_a (const char *type,
const char *supertype)
{
gboolean res;
g_return_val_if_fail (type != NULL, FALSE);
g_return_val_if_fail (supertype != NULL, FALSE);
G_LOCK (gio_xdgmime);
res = xdg_mime_mime_type_subclass (type, supertype);
G_UNLOCK (gio_xdgmime);
return res;
}
/**
* g_content_type_is_unknown:
* @type: a content type string.
*
* Checks if the content type is the generic "unknown" type.
* On unix this is the "application/octet-stream" mimetype,
* while on win32 it is "*".
*
* Returns: %TRUE if the type is the unknown type.
**/
gboolean
g_content_type_is_unknown (const char *type)
{
g_return_val_if_fail (type != NULL, FALSE);
return strcmp (XDG_MIME_TYPE_UNKNOWN, type) == 0;
}
typedef enum {
MIME_TAG_TYPE_OTHER,
MIME_TAG_TYPE_COMMENT
} MimeTagType;
typedef struct {
int current_type;
int current_lang_level;
int comment_lang_level;
char *comment;
} MimeParser;
static int
language_level (const char *lang)
{
const char * const *lang_list;
int i;
/* The returned list is sorted from most desirable to least
desirable and always contains the default locale "C". */
lang_list = g_get_language_names ();
for (i = 0; lang_list[i]; i++)
if (strcmp (lang_list[i], lang) == 0)
return 1000-i;
return 0;
}
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)
{
int i;
const char *lang;
MimeParser *parser = user_data;
if (strcmp (element_name, "comment") == 0)
{
lang = "C";
for (i = 0; attribute_names[i]; i++)
if (strcmp (attribute_names[i], "xml:lang") == 0)
{
lang = attribute_values[i];
break;
}
parser->current_lang_level = language_level (lang);
parser->current_type = MIME_TAG_TYPE_COMMENT;
}
else
parser->current_type = MIME_TAG_TYPE_OTHER;
}
static void
mime_info_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error)
{
MimeParser *parser = user_data;
parser->current_type = MIME_TAG_TYPE_OTHER;
}
static void
mime_info_text (GMarkupParseContext *context,
const gchar *text,
gsize text_len,
gpointer user_data,
GError **error)
{
MimeParser *parser = user_data;
if (parser->current_type == MIME_TAG_TYPE_COMMENT &&
parser->current_lang_level > parser->comment_lang_level)
{
g_free (parser->comment);
parser->comment = g_strndup (text, text_len);
parser->comment_lang_level = parser->current_lang_level;
}
}
static char *
load_comment_for_mime_helper (const char *dir,
const char *basename)
{
GMarkupParseContext *context;
char *filename, *data;
gsize len;
gboolean res;
MimeParser parse_data = {0};
GMarkupParser parser = {
mime_info_start_element,
mime_info_end_element,
mime_info_text
};
filename = g_build_filename (dir, "mime", basename, NULL);
res = g_file_get_contents (filename, &data, &len, NULL);
g_free (filename);
if (!res)
return NULL;
context = g_markup_parse_context_new (&parser, 0, &parse_data, NULL);
res = g_markup_parse_context_parse (context, data, len, NULL);
g_free (data);
g_markup_parse_context_free (context);
if (!res)
return NULL;
return parse_data.comment;
}
static char *
load_comment_for_mime (const char *mimetype)
{
const char * const* dirs;
char *basename;
char *comment;
int i;
basename = g_strdup_printf ("%s.xml", mimetype);
comment = load_comment_for_mime_helper (g_get_user_data_dir (), basename);
if (comment)
{
g_free (basename);
return comment;
}
dirs = g_get_system_data_dirs ();
for (i = 0; dirs[i] != NULL; i++)
{
comment = load_comment_for_mime_helper (dirs[i], basename);
if (comment)
{
g_free (basename);
return comment;
}
}
g_free (basename);
return g_strdup_printf (_("%s type"), mimetype);
}
/**
* g_content_type_get_description:
* @type: a content type string.
*
* Gets the human readable description of the content type.
*
* Returns: a short description of the content type @type.
**/
char *
g_content_type_get_description (const char *type)
{
static GHashTable *type_comment_cache = NULL;
char *comment;
g_return_val_if_fail (type != NULL, NULL);
G_LOCK (gio_xdgmime);
type = xdg_mime_unalias_mime_type (type);
if (type_comment_cache == NULL)
type_comment_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
comment = g_hash_table_lookup (type_comment_cache, type);
comment = g_strdup (comment);
G_UNLOCK (gio_xdgmime);
if (comment != NULL)
return comment;
comment = load_comment_for_mime (type);
G_LOCK (gio_xdgmime);
g_hash_table_insert (type_comment_cache,
g_strdup (type),
g_strdup (comment));
G_UNLOCK (gio_xdgmime);
return comment;
}
/**
* g_content_type_get_mime_type:
* @type: a content type string.
*
* Gets the mime-type for the content type. If one is registered
*
* Returns: the registered mime-type for the given @type, or NULL if unknown.
**/
char *
g_content_type_get_mime_type (const char *type)
{
g_return_val_if_fail (type != NULL, NULL);
return g_strdup (type);
}
/**
* g_content_type_get_icon:
* @type: a content type string.
*
* Gets the icon for a content type.
*
* Returns: #GIcon corresponding to the content type.
**/
GIcon *
g_content_type_get_icon (const char *type)
{
char *mimetype_icon, *generic_mimetype_icon, *q;
char *xdg_mimetype_icon, *legacy_mimetype_icon;
char *xdg_mimetype_generic_icon;
char *icon_names[4];
int n = 0;
const char *p;
GIcon *themed_icon;
g_return_val_if_fail (type != NULL, NULL);
G_LOCK (gio_xdgmime);
xdg_mimetype_icon = g_strdup (xdg_mime_get_icon (type));
xdg_mimetype_generic_icon = g_strdup (xdg_mime_get_generic_icon (type));
G_UNLOCK (gio_xdgmime);
mimetype_icon = g_strdup (type);
while ((q = strchr (mimetype_icon, '/')) != NULL)
*q = '-';
p = strchr (type, '/');
if (p == NULL)
p = type + strlen (type);
/* Not all icons have migrated to the new icon theme spec, look for old names too */
legacy_mimetype_icon = g_strconcat ("gnome-mime-", mimetype_icon, NULL);
generic_mimetype_icon = g_malloc (p - type + strlen ("-x-generic") + 1);
memcpy (generic_mimetype_icon, type, p - type);
memcpy (generic_mimetype_icon + (p - type), "-x-generic", strlen ("-x-generic"));
generic_mimetype_icon[(p - type) + strlen ("-x-generic")] = 0;
if (xdg_mimetype_icon)
icon_names[n++] = xdg_mimetype_icon;
icon_names[n++] = mimetype_icon;
icon_names[n++] = legacy_mimetype_icon;
if (xdg_mimetype_generic_icon)
icon_names[n++] = xdg_mimetype_generic_icon;
icon_names[n++] = generic_mimetype_icon;
themed_icon = g_themed_icon_new_from_names (icon_names, n);
g_free (xdg_mimetype_icon);
g_free (xdg_mimetype_generic_icon);
g_free (mimetype_icon);
g_free (legacy_mimetype_icon);
g_free (generic_mimetype_icon);
return themed_icon;
}
/**
* g_content_type_can_be_executable:
* @type: a content type string.
*
* Checks if a content type can be executable. Note that for instance
* things like text files can be executables (i.e. scripts and batch files).
*
* Returns: %TRUE if the file type corresponds to a type that
* can be executable, %FALSE otherwise.
**/
gboolean
g_content_type_can_be_executable (const char *type)
{
g_return_val_if_fail (type != NULL, FALSE);
if (g_content_type_is_a (type, "application/x-executable") ||
g_content_type_is_a (type, "text/plain"))
return TRUE;
return FALSE;
}
static gboolean
looks_like_text (const guchar *data, gsize data_size)
{
gsize i;
char c;
for (i = 0; i < data_size; i++)
{
c = data[i];
if (g_ascii_iscntrl (c) &&
!g_ascii_isspace (c))
return FALSE;
}
return TRUE;
}
/**
* g_content_type_from_mime_type:
* @mime_type: a mime type string.
*
* Tries to find a content type based on the mime type name.
*
* Returns: Newly allocated string with content type or NULL when does not know.
*
* Since: 2.18
**/
char *
g_content_type_from_mime_type (const char *mime_type)
{
char *umime;
g_return_val_if_fail (mime_type != NULL, NULL);
G_LOCK (gio_xdgmime);
/* mime type and content type are same on unixes */
umime = g_strdup (xdg_mime_unalias_mime_type (mime_type));
G_UNLOCK (gio_xdgmime);
return umime;
}
/**
* g_content_type_guess:
* @filename: a string, or %NULL
* @data: a stream of data, or %NULL
* @data_size: the size of @data
* @result_uncertain: a flag indicating the certainty of the result
*
* Guesses the content type based on example data. If the function is
* uncertain, @result_uncertain will be set to %TRUE. Either @filename
* or @data may be %NULL, in which case the guess will be based solely
* on the other argument.
*
* Returns: a string indicating a guessed content type for the
* given data.
**/
char *
g_content_type_guess (const char *filename,
const guchar *data,
gsize data_size,
gboolean *result_uncertain)
{
char *basename;
const char *name_mimetypes[10], *sniffed_mimetype;
char *mimetype;
int i;
int n_name_mimetypes;
int sniffed_prio;
sniffed_prio = 0;
n_name_mimetypes = 0;
sniffed_mimetype = XDG_MIME_TYPE_UNKNOWN;
if (result_uncertain)
*result_uncertain = FALSE;
G_LOCK (gio_xdgmime);
if (filename)
{
i = strlen (filename);
if (filename[i - 1] == '/')
{
name_mimetypes[0] = "inode/directory";
name_mimetypes[1] = NULL;
n_name_mimetypes = 1;
if (result_uncertain)
*result_uncertain = TRUE;
}
else
{
basename = g_path_get_basename (filename);
n_name_mimetypes = xdg_mime_get_mime_types_from_file_name (basename, name_mimetypes, 10);
g_free (basename);
}
}
/* Got an extension match, and no conflicts. This is it. */
if (n_name_mimetypes == 1)
{
G_UNLOCK (gio_xdgmime);
return g_strdup (name_mimetypes[0]);
}
if (data)
{
sniffed_mimetype = xdg_mime_get_mime_type_for_data (data, data_size, &sniffed_prio);
if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN &&
data &&
looks_like_text (data, data_size))
sniffed_mimetype = "text/plain";
/* For security reasons we don't ever want to sniff desktop files
* where we know the filename and it doesn't have a .desktop extension.
* This is because desktop files allow executing any application and
* we don't want to make it possible to hide them looking like something
* else.
*/
if (filename != NULL &&
strcmp (sniffed_mimetype, "application/x-desktop") == 0)
sniffed_mimetype = "text/plain";
}
if (n_name_mimetypes == 0)
{
if (sniffed_mimetype == XDG_MIME_TYPE_UNKNOWN &&
result_uncertain)
*result_uncertain = TRUE;
mimetype = g_strdup (sniffed_mimetype);
}
else
{
mimetype = NULL;
if (sniffed_mimetype != XDG_MIME_TYPE_UNKNOWN)
{
if (sniffed_prio >= 80) /* High priority sniffing match, use that */
mimetype = g_strdup (sniffed_mimetype);
else
{
/* There are conflicts between the name matches and we have a sniffed
type, use that as a tie breaker. */
for (i = 0; i < n_name_mimetypes; i++)
{
if ( xdg_mime_mime_type_subclass (name_mimetypes[i], sniffed_mimetype))
{
/* This nametype match is derived from (or the same as) the sniffed type).
This is probably it. */
mimetype = g_strdup (name_mimetypes[i]);
break;
}
}
}
}
if (mimetype == NULL)
{
/* Conflicts, and sniffed type was no help or not there. Guess on the first one */
mimetype = g_strdup (name_mimetypes[0]);
if (result_uncertain)
*result_uncertain = TRUE;
}
}
G_UNLOCK (gio_xdgmime);
return mimetype;
}
static void
enumerate_mimetypes_subdir (const char *dir,
const char *prefix,
GHashTable *mimetypes)
{
DIR *d;
struct dirent *ent;
char *mimetype;
d = opendir (dir);
if (d)
{
while ((ent = readdir (d)) != NULL)
{
if (g_str_has_suffix (ent->d_name, ".xml"))
{
mimetype = g_strdup_printf ("%s/%.*s", prefix, (int) strlen (ent->d_name) - 4, ent->d_name);
g_hash_table_replace (mimetypes, mimetype, NULL);
}
}
closedir (d);
}
}
static void
enumerate_mimetypes_dir (const char *dir,
GHashTable *mimetypes)
{
DIR *d;
struct dirent *ent;
char *mimedir;
char *name;
mimedir = g_build_filename (dir, "mime", NULL);
d = opendir (mimedir);
if (d)
{
while ((ent = readdir (d)) != NULL)
{
if (strcmp (ent->d_name, "packages") != 0)
{
name = g_build_filename (mimedir, ent->d_name, NULL);
if (g_file_test (name, G_FILE_TEST_IS_DIR))
enumerate_mimetypes_subdir (name, ent->d_name, mimetypes);
g_free (name);
}
}
closedir (d);
}
g_free (mimedir);
}
/**
* g_content_types_get_registered:
*
* Gets a list of strings containing all the registered content types
* known to the system. The list and its data should be freed using
* @g_list_foreach(list, g_free, NULL) and @g_list_free(list)
* Returns: #GList of the registered content types.
**/
GList *
g_content_types_get_registered (void)
{
const char * const* dirs;
GHashTable *mimetypes;
GHashTableIter iter;
gpointer key;
int i;
GList *l;
mimetypes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
enumerate_mimetypes_dir (g_get_user_data_dir (), mimetypes);
dirs = g_get_system_data_dirs ();
for (i = 0; dirs[i] != NULL; i++)
enumerate_mimetypes_dir (dirs[i], mimetypes);
l = NULL;
g_hash_table_iter_init (&iter, mimetypes);
while (g_hash_table_iter_next (&iter, &key, NULL))
{
l = g_list_prepend (l, key);
g_hash_table_iter_steal (&iter);
}
g_hash_table_destroy (mimetypes);
return l;
}
/* tree magic data */
static GList *tree_matches = NULL;
static gboolean need_reload = FALSE;
G_LOCK_DEFINE_STATIC (gio_treemagic);
typedef struct
{
gchar *path;
GFileType type;
guint match_case : 1;
guint executable : 1;
guint non_empty : 1;
guint on_disc : 1;
gchar *mimetype;
GList *matches;
} TreeMatchlet;
typedef struct
{
gchar *contenttype;
gint priority;
GList *matches;
} TreeMatch;
static void
tree_matchlet_free (TreeMatchlet *matchlet)
{
g_list_foreach (matchlet->matches, (GFunc)tree_matchlet_free, NULL);
g_list_free (matchlet->matches);
g_free (matchlet->path);
g_free (matchlet->mimetype);
g_slice_free (TreeMatchlet, matchlet);
}
static void
tree_match_free (TreeMatch *match)
{
g_list_foreach (match->matches, (GFunc)tree_matchlet_free, NULL);
g_list_free (match->matches);
g_free (match->contenttype);
g_slice_free (TreeMatch, match);
}
static TreeMatch *
parse_header (gchar *line)
{
gint len;
gchar *s;
TreeMatch *match;
len = strlen (line);
if (line[0] != '[' || line[len - 1] != ']')
return NULL;
line[len - 1] = 0;
s = strchr (line, ':');
match = g_slice_new0 (TreeMatch);
match->priority = atoi (line + 1);
match->contenttype = g_strdup (s + 1);
return match;
}
static TreeMatchlet *
parse_match_line (gchar *line,
gint *depth)
{
gchar *s, *p;
TreeMatchlet *matchlet;
gchar **parts;
gint i;
matchlet = g_slice_new0 (TreeMatchlet);
if (line[0] == '>')
{
*depth = 0;
s = line;
}
else
{
*depth = atoi (line);
s = strchr (line, '>');
}
s += 2;
p = strchr (s, '"');
*p = 0;
matchlet->path = g_strdup (s);
s = p + 1;
parts = g_strsplit (s, ",", 0);
if (strcmp (parts[0], "=file") == 0)
matchlet->type = G_FILE_TYPE_REGULAR;
else if (strcmp (parts[0], "=directory") == 0)
matchlet->type = G_FILE_TYPE_DIRECTORY;
else if (strcmp (parts[0], "=link") == 0)
matchlet->type = G_FILE_TYPE_SYMBOLIC_LINK;
else
matchlet->type = G_FILE_TYPE_UNKNOWN;
for (i = 1; parts[i]; i++)
{
if (strcmp (parts[i], "executable") == 0)
matchlet->executable = 1;
else if (strcmp (parts[i], "match-case") == 0)
matchlet->match_case = 1;
else if (strcmp (parts[i], "non-empty") == 0)
matchlet->non_empty = 1;
else if (strcmp (parts[i], "on-disc") == 0)
matchlet->on_disc = 1;
else
matchlet->mimetype = g_strdup (parts[i]);
}
g_strfreev (parts);
return matchlet;
}
static gint
cmp_match (gconstpointer a, gconstpointer b)
{
const TreeMatch *aa = (const TreeMatch *)a;
const TreeMatch *bb = (const TreeMatch *)b;
return bb->priority - aa->priority;
}
static void
insert_match (TreeMatch *match)
{
tree_matches = g_list_insert_sorted (tree_matches, match, cmp_match);
}
static void
insert_matchlet (TreeMatch *match,
TreeMatchlet *matchlet,
gint depth)
{
if (depth == 0)
match->matches = g_list_append (match->matches, matchlet);
else
{
GList *last;
TreeMatchlet *m;
last = g_list_last (match->matches);
if (!last)
{
tree_matchlet_free (matchlet);
g_warning ("can't insert tree matchlet at depth %d", depth);
return;
}
m = (TreeMatchlet *) last->data;
while (--depth > 0)
{
last = g_list_last (m->matches);
if (!last)
{
tree_matchlet_free (matchlet);
g_warning ("can't insert tree matchlet at depth %d", depth);
return;
}
m = (TreeMatchlet *) last->data;
}
m->matches = g_list_append (m->matches, matchlet);
}
}
static void
read_tree_magic_from_directory (const gchar *prefix)
{
gchar *filename;
gchar *text;
gsize len;
gchar **lines;
gint i;
TreeMatch *match;
TreeMatchlet *matchlet;
gint depth;
filename = g_build_filename (prefix, "mime", "treemagic", NULL);
if (g_file_get_contents (filename, &text, &len, NULL))
{
if (strcmp (text, "MIME-TreeMagic") == 0)
{
lines = g_strsplit (text + strlen ("MIME-TreeMagic") + 2, "\n", 0);
match = NULL;
for (i = 0; lines[i] && lines[i][0]; i++)
{
if (lines[i][0] == '[')
{
match = parse_header (lines[i]);
insert_match (match);
}
else
{
matchlet = parse_match_line (lines[i], &depth);
insert_matchlet (match, matchlet, depth);
}
}
g_strfreev (lines);
}
else
g_warning ("%s: header not found, skipping\n", filename);
g_free (text);
}
g_free (filename);
}
static void
xdg_mime_reload (void *user_data)
{
need_reload = TRUE;
}
static void
tree_magic_shutdown (void)
{
g_list_foreach (tree_matches, (GFunc)tree_match_free, NULL);
g_list_free (tree_matches);
tree_matches = NULL;
}
static void
tree_magic_init (void)
{
static gboolean initialized = FALSE;
const gchar *dir;
const gchar * const * dirs;
int i;
if (!initialized)
{
initialized = TRUE;
xdg_mime_register_reload_callback (xdg_mime_reload, NULL, NULL);
need_reload = TRUE;
}
if (need_reload)
{
need_reload = FALSE;
tree_magic_shutdown ();
dir = g_get_user_data_dir ();
read_tree_magic_from_directory (dir);
dirs = g_get_system_data_dirs ();
for (i = 0; dirs[i]; i++)
read_tree_magic_from_directory (dirs[i]);
}
}
/* a filtering enumerator */
typedef struct
{
gchar *path;
gint depth;
gboolean ignore_case;
gchar **components;
gchar **case_components;
GFileEnumerator **enumerators;
GFile **children;
} Enumerator;
static gboolean
component_match (Enumerator *e,
gint depth,
const gchar *name)
{
gchar *case_folded, *key;
gboolean found;
if (strcmp (name, e->components[depth]) == 0)
return TRUE;
if (!e->ignore_case)
return FALSE;
case_folded = g_utf8_casefold (name, -1);
key = g_utf8_collate_key (case_folded, -1);
found = strcmp (key, e->case_components[depth]) == 0;
g_free (case_folded);
g_free (key);
return found;
}
static GFile *
next_match_recurse (Enumerator *e,
gint depth)
{
GFile *file;
GFileInfo *info;
const gchar *name;
while (TRUE)
{
if (e->enumerators[depth] == NULL)
{
if (depth > 0)
{
file = next_match_recurse (e, depth - 1);
if (file)
{
e->children[depth] = file;
e->enumerators[depth] = g_file_enumerate_children (file,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
}
}
if (e->enumerators[depth] == NULL)
return NULL;
}
while ((info = g_file_enumerator_next_file (e->enumerators[depth], NULL, NULL)))
{
name = g_file_info_get_name (info);
if (component_match (e, depth, name))
{
file = g_file_get_child (e->children[depth], name);
g_object_unref (info);
return file;
}
g_object_unref (info);
}
g_object_unref (e->enumerators[depth]);
e->enumerators[depth] = NULL;
g_object_unref (e->children[depth]);
e->children[depth] = NULL;
}
}
static GFile *
enumerator_next (Enumerator *e)
{
return next_match_recurse (e, e->depth - 1);
}
static Enumerator *
enumerator_new (GFile *root,
const char *path,
gboolean ignore_case)
{
Enumerator *e;
gint i;
gchar *case_folded;
e = g_new0 (Enumerator, 1);
e->path = g_strdup (path);
e->ignore_case = ignore_case;
e->components = g_strsplit (e->path, G_DIR_SEPARATOR_S, -1);
e->depth = g_strv_length (e->components);
if (e->ignore_case)
{
e->case_components = g_new0 (char *, e->depth + 1);
for (i = 0; e->components[i]; i++)
{
case_folded = g_utf8_casefold (e->components[i], -1);
e->case_components[i] = g_utf8_collate_key (case_folded, -1);
g_free (case_folded);
}
}
e->children = g_new0 (GFile *, e->depth);
e->children[0] = g_object_ref (root);
e->enumerators = g_new0 (GFileEnumerator *, e->depth);
e->enumerators[0] = g_file_enumerate_children (root,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
return e;
}
static void
enumerator_free (Enumerator *e)
{
gint i;
for (i = 0; i < e->depth; i++)
{
if (e->enumerators[i])
g_object_unref (e->enumerators[i]);
if (e->children[i])
g_object_unref (e->children[i]);
}
g_free (e->enumerators);
g_free (e->children);
g_strfreev (e->components);
if (e->case_components)
g_strfreev (e->case_components);
g_free (e->path);
g_free (e);
}
static gboolean
matchlet_match (TreeMatchlet *matchlet,
GFile *root)
{
GFile *file;
GFileInfo *info;
gboolean result;
const gchar *attrs;
Enumerator *e;
GList *l;
e = enumerator_new (root, matchlet->path, !matchlet->match_case);
do
{
file = enumerator_next (e);
if (!file)
{
enumerator_free (e);
return FALSE;
}
if (matchlet->mimetype)
attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ","
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE;
else
attrs = G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE;
info = g_file_query_info (file,
attrs,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (info)
{
result = TRUE;
if (matchlet->type != G_FILE_TYPE_UNKNOWN &&
g_file_info_get_file_type (info) != matchlet->type)
result = FALSE;
if (matchlet->executable &&
!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
result = FALSE;
}
else
result = FALSE;
if (result && matchlet->non_empty)
{
GFileEnumerator *child_enum;
GFileInfo *child_info;
child_enum = g_file_enumerate_children (file,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (child_enum)
{
child_info = g_file_enumerator_next_file (child_enum, NULL, NULL);
if (child_info)
g_object_unref (child_info);
else
result = FALSE;
g_object_unref (child_enum);
}
else
result = FALSE;
}
if (result && matchlet->mimetype)
{
if (strcmp (matchlet->mimetype, g_file_info_get_content_type (info)) != 0)
result = FALSE;
}
g_object_unref (info);
g_object_unref (file);
}
while (!result);
enumerator_free (e);
if (!matchlet->matches)
return TRUE;
for (l = matchlet->matches; l; l = l->next)
{
TreeMatchlet *submatchlet;
submatchlet = l->data;
if (matchlet_match (submatchlet, root))
return TRUE;
}
return FALSE;
}
static void
match_match (TreeMatch *match,
GFile *root,
GPtrArray *types)
{
GList *l;
for (l = match->matches; l; l = l->next)
{
TreeMatchlet *matchlet = l->data;
if (matchlet_match (matchlet, root))
{
g_ptr_array_add (types, g_strdup (match->contenttype));
break;
}
}
}
/**
* g_content_type_guess_for_tree:
* @root: the root of the tree to guess a type for
*
* Tries to guess the type of the tree with root @root, by
* looking at the files it contains. The result is an array
* of content types, with the best guess coming first.
*
* The types returned all have the form x-content/foo, e.g.
* x-content/audio-cdda (for audio CDs) or x-content/image-dcf
* (for a camera memory card). See the <ulink url="http://www.freedesktop.org/wiki/Specifications/shared-mime-info-spec">shared-mime-info</ulink>
* specification for more on x-content types.
*
* This function is useful in the implementation of g_mount_guess_content_type().
*
* Returns: an %NULL-terminated array of zero or more content types, or %NULL.
* Free with g_strfreev()
*
* Since: 2.18
*/
char **
g_content_type_guess_for_tree (GFile *root)
{
GPtrArray *types;
GList *l;
types = g_ptr_array_new ();
G_LOCK (gio_treemagic);
tree_magic_init ();
for (l = tree_matches; l; l = l->next)
{
TreeMatch *match = l->data;
match_match (match, root, types);
}
G_UNLOCK (gio_treemagic);
g_ptr_array_add (types, NULL);
return (char **)g_ptr_array_free (types, FALSE);
}
#endif /* Unix version */
#define __G_CONTENT_TYPE_C__
#include "gioaliasdef.c"