mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-11-02 07:36:17 +01:00
9fdf70ba5f
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
1672 lines
39 KiB
C
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, ®_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"
|