/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * * SPDX-License-Identifier: LGPL-2.1-or-later * * 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.1 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, see . * * Author: Alexander Larsson */ #include "config.h" #include #include #include #include #include #include #include "gdummyfile.h" #include "gfile.h" static void g_dummy_file_file_iface_init (GFileIface *iface); typedef struct { char *scheme; char *userinfo; char *host; int port; /* -1 => not in uri */ char *path; char *query; char *fragment; } GDecodedUri; struct _GDummyFile { GObject parent_instance; GDecodedUri *decoded_uri; char *text_uri; }; #define g_dummy_file_get_type _g_dummy_file_get_type G_DEFINE_TYPE_WITH_CODE (GDummyFile, g_dummy_file, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_FILE, g_dummy_file_file_iface_init)) #define SUB_DELIM_CHARS "!$&'()*+,;=" static char * _g_encode_uri (GDecodedUri *decoded); static void _g_decoded_uri_free (GDecodedUri *decoded); static GDecodedUri *_g_decode_uri (const char *uri); static GDecodedUri *_g_decoded_uri_new (void); static char * unescape_string (const gchar *escaped_string, const gchar *escaped_string_end, const gchar *illegal_characters); static void g_string_append_encoded (GString *string, const char *encoded, const char *reserved_chars_allowed); static void g_dummy_file_finalize (GObject *object) { GDummyFile *dummy; dummy = G_DUMMY_FILE (object); if (dummy->decoded_uri) _g_decoded_uri_free (dummy->decoded_uri); g_free (dummy->text_uri); G_OBJECT_CLASS (g_dummy_file_parent_class)->finalize (object); } static void g_dummy_file_class_init (GDummyFileClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = g_dummy_file_finalize; } static void g_dummy_file_init (GDummyFile *dummy) { } GFile * _g_dummy_file_new (const char *uri) { GDummyFile *dummy; g_return_val_if_fail (uri != NULL, NULL); dummy = g_object_new (G_TYPE_DUMMY_FILE, NULL); dummy->text_uri = g_strdup (uri); dummy->decoded_uri = _g_decode_uri (uri); return G_FILE (dummy); } static gboolean g_dummy_file_is_native (GFile *file) { return FALSE; } static char * g_dummy_file_get_basename (GFile *file) { GDummyFile *dummy = G_DUMMY_FILE (file); if (dummy->decoded_uri) return g_path_get_basename (dummy->decoded_uri->path); return NULL; } static char * g_dummy_file_get_path (GFile *file) { return NULL; } static char * g_dummy_file_get_uri (GFile *file) { return g_strdup (G_DUMMY_FILE (file)->text_uri); } static char * g_dummy_file_get_parse_name (GFile *file) { return g_strdup (G_DUMMY_FILE (file)->text_uri); } static GFile * g_dummy_file_get_parent (GFile *file) { GDummyFile *dummy = G_DUMMY_FILE (file); GFile *parent; char *dirname; char *uri; GDecodedUri new_decoded_uri; if (dummy->decoded_uri == NULL || g_strcmp0 (dummy->decoded_uri->path, "/") == 0) return NULL; dirname = g_path_get_dirname (dummy->decoded_uri->path); if (strcmp (dirname, ".") == 0) { g_free (dirname); return NULL; } new_decoded_uri = *dummy->decoded_uri; new_decoded_uri.path = dirname; uri = _g_encode_uri (&new_decoded_uri); g_free (dirname); parent = _g_dummy_file_new (uri); g_free (uri); return parent; } static GFile * g_dummy_file_dup (GFile *file) { GDummyFile *dummy = G_DUMMY_FILE (file); return _g_dummy_file_new (dummy->text_uri); } static guint g_dummy_file_hash (GFile *file) { GDummyFile *dummy = G_DUMMY_FILE (file); return g_str_hash (dummy->text_uri); } static gboolean g_dummy_file_equal (GFile *file1, GFile *file2) { GDummyFile *dummy1 = G_DUMMY_FILE (file1); GDummyFile *dummy2 = G_DUMMY_FILE (file2); return g_str_equal (dummy1->text_uri, dummy2->text_uri); } static int safe_strcmp (const char *a, const char *b) { if (a == NULL) a = ""; if (b == NULL) b = ""; return strcmp (a, b); } static gboolean uri_same_except_path (GDecodedUri *a, GDecodedUri *b) { if (safe_strcmp (a->scheme, b->scheme) != 0) return FALSE; if (safe_strcmp (a->userinfo, b->userinfo) != 0) return FALSE; if (safe_strcmp (a->host, b->host) != 0) return FALSE; if (a->port != b->port) return FALSE; return TRUE; } static const char * match_prefix (const char *path, const char *prefix) { size_t prefix_len; prefix_len = strlen (prefix); if (strncmp (path, prefix, prefix_len) != 0) return NULL; return path + prefix_len; } static gboolean g_dummy_file_prefix_matches (GFile *parent, GFile *descendant) { GDummyFile *parent_dummy = G_DUMMY_FILE (parent); GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant); const char *remainder; if (parent_dummy->decoded_uri != NULL && descendant_dummy->decoded_uri != NULL) { if (uri_same_except_path (parent_dummy->decoded_uri, descendant_dummy->decoded_uri)) { remainder = match_prefix (descendant_dummy->decoded_uri->path, parent_dummy->decoded_uri->path); if (remainder != NULL && *remainder == '/') { while (*remainder == '/') remainder++; if (*remainder != 0) return TRUE; } } } else { remainder = match_prefix (descendant_dummy->text_uri, parent_dummy->text_uri); if (remainder != NULL && *remainder == '/') { while (*remainder == '/') remainder++; if (*remainder != 0) return TRUE; } } return FALSE; } static char * g_dummy_file_get_relative_path (GFile *parent, GFile *descendant) { GDummyFile *parent_dummy = G_DUMMY_FILE (parent); GDummyFile *descendant_dummy = G_DUMMY_FILE (descendant); const char *remainder; if (parent_dummy->decoded_uri != NULL && descendant_dummy->decoded_uri != NULL) { if (uri_same_except_path (parent_dummy->decoded_uri, descendant_dummy->decoded_uri)) { remainder = match_prefix (descendant_dummy->decoded_uri->path, parent_dummy->decoded_uri->path); if (remainder != NULL && *remainder == '/') { while (*remainder == '/') remainder++; if (*remainder != 0) return g_strdup (remainder); } } } else { remainder = match_prefix (descendant_dummy->text_uri, parent_dummy->text_uri); if (remainder != NULL && *remainder == '/') { while (*remainder == '/') remainder++; if (*remainder != 0) return unescape_string (remainder, NULL, "/"); } } return NULL; } static GFile * g_dummy_file_resolve_relative_path (GFile *file, const char *relative_path) { GDummyFile *dummy = G_DUMMY_FILE (file); GFile *child; char *uri; GDecodedUri new_decoded_uri; GString *str; if (dummy->decoded_uri == NULL) { str = g_string_new (dummy->text_uri); g_string_append (str, "/"); g_string_append_encoded (str, relative_path, SUB_DELIM_CHARS ":@/"); child = _g_dummy_file_new (str->str); g_string_free (str, TRUE); } else { new_decoded_uri = *dummy->decoded_uri; if (g_path_is_absolute (relative_path)) new_decoded_uri.path = g_strdup (relative_path); else new_decoded_uri.path = g_build_filename (new_decoded_uri.path, relative_path, NULL); uri = _g_encode_uri (&new_decoded_uri); g_free (new_decoded_uri.path); child = _g_dummy_file_new (uri); g_free (uri); } return child; } static GFile * g_dummy_file_get_child_for_display_name (GFile *file, const char *display_name, GError **error) { return g_file_get_child (file, display_name); } static gboolean g_dummy_file_has_uri_scheme (GFile *file, const char *uri_scheme) { GDummyFile *dummy = G_DUMMY_FILE (file); if (dummy->decoded_uri) return g_ascii_strcasecmp (uri_scheme, dummy->decoded_uri->scheme) == 0; return FALSE; } static char * g_dummy_file_get_uri_scheme (GFile *file) { GDummyFile *dummy = G_DUMMY_FILE (file); if (dummy->decoded_uri) return g_strdup (dummy->decoded_uri->scheme); return NULL; } static void g_dummy_file_file_iface_init (GFileIface *iface) { iface->dup = g_dummy_file_dup; iface->hash = g_dummy_file_hash; iface->equal = g_dummy_file_equal; iface->is_native = g_dummy_file_is_native; iface->has_uri_scheme = g_dummy_file_has_uri_scheme; iface->get_uri_scheme = g_dummy_file_get_uri_scheme; iface->get_basename = g_dummy_file_get_basename; iface->get_path = g_dummy_file_get_path; iface->get_uri = g_dummy_file_get_uri; iface->get_parse_name = g_dummy_file_get_parse_name; iface->get_parent = g_dummy_file_get_parent; iface->prefix_matches = g_dummy_file_prefix_matches; iface->get_relative_path = g_dummy_file_get_relative_path; iface->resolve_relative_path = g_dummy_file_resolve_relative_path; iface->get_child_for_display_name = g_dummy_file_get_child_for_display_name; iface->supports_thread_contexts = TRUE; } /* Uri handling helper functions: */ static int unescape_character (const char *scanner) { int first_digit; int second_digit; first_digit = g_ascii_xdigit_value (*scanner++); if (first_digit < 0) return -1; second_digit = g_ascii_xdigit_value (*scanner++); if (second_digit < 0) return -1; return (first_digit << 4) | second_digit; } static char * unescape_string (const gchar *escaped_string, const gchar *escaped_string_end, const gchar *illegal_characters) { const gchar *in; gchar *out, *result; gint character; if (escaped_string == NULL) return NULL; if (escaped_string_end == NULL) escaped_string_end = escaped_string + strlen (escaped_string); result = g_malloc (escaped_string_end - escaped_string + 1); out = result; for (in = escaped_string; in < escaped_string_end; in++) { character = *in; if (*in == '%') { in++; if (escaped_string_end - in < 2) { g_free (result); return NULL; } character = unescape_character (in); /* Check for an illegal character. We consider '\0' illegal here. */ if (character <= 0 || (illegal_characters != NULL && strchr (illegal_characters, (char)character) != NULL)) { g_free (result); return NULL; } in++; /* The other char will be eaten in the loop header */ } *out++ = (char)character; } *out = '\0'; g_warn_if_fail ((gsize) (out - result) <= strlen (escaped_string)); return result; } void _g_decoded_uri_free (GDecodedUri *decoded) { if (decoded == NULL) return; g_free (decoded->scheme); g_free (decoded->query); g_free (decoded->fragment); g_free (decoded->userinfo); g_free (decoded->host); g_free (decoded->path); g_free (decoded); } GDecodedUri * _g_decoded_uri_new (void) { GDecodedUri *uri; uri = g_new0 (GDecodedUri, 1); uri->port = -1; return uri; } GDecodedUri * _g_decode_uri (const char *uri) { GDecodedUri *decoded; const char *p, *in, *hier_part_start, *hier_part_end, *query_start, *fragment_start; char *out; char c; /* From RFC 3986 Decodes: * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] */ p = uri; /* Decode scheme: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ if (!g_ascii_isalpha (*p)) return NULL; while (1) { c = *p++; if (c == ':') break; if (!(g_ascii_isalnum(c) || c == '+' || c == '-' || c == '.')) return NULL; } decoded = _g_decoded_uri_new (); decoded->scheme = g_malloc (p - uri); out = decoded->scheme; for (in = uri; in < p - 1; in++) *out++ = g_ascii_tolower (*in); *out = 0; hier_part_start = p; query_start = strchr (p, '?'); if (query_start) { hier_part_end = query_start++; fragment_start = strchr (query_start, '#'); if (fragment_start) { decoded->query = g_strndup (query_start, fragment_start - query_start); decoded->fragment = g_strdup (fragment_start+1); } else { decoded->query = g_strdup (query_start); decoded->fragment = NULL; } } else { /* No query */ decoded->query = NULL; fragment_start = strchr (p, '#'); if (fragment_start) { hier_part_end = fragment_start++; decoded->fragment = g_strdup (fragment_start); } else { hier_part_end = p + strlen (p); decoded->fragment = NULL; } } /* 3: hier-part = "//" authority path-abempty / path-absolute / path-rootless / path-empty */ if (hier_part_start[0] == '/' && hier_part_start[1] == '/') { const char *authority_start, *authority_end; const char *userinfo_start, *userinfo_end; const char *host_start, *host_end; const char *port_start; authority_start = hier_part_start + 2; /* authority is always followed by / or nothing */ authority_end = memchr (authority_start, '/', hier_part_end - authority_start); if (authority_end == NULL) authority_end = hier_part_end; /* 3.2: authority = [ userinfo "@" ] host [ ":" port ] */ userinfo_end = memchr (authority_start, '@', authority_end - authority_start); if (userinfo_end) { userinfo_start = authority_start; decoded->userinfo = unescape_string (userinfo_start, userinfo_end, NULL); if (decoded->userinfo == NULL) { _g_decoded_uri_free (decoded); return NULL; } host_start = userinfo_end + 1; } else host_start = authority_start; port_start = memchr (host_start, ':', authority_end - host_start); if (port_start) { host_end = port_start++; decoded->port = atoi(port_start); } else { host_end = authority_end; decoded->port = -1; } decoded->host = g_strndup (host_start, host_end - host_start); hier_part_start = authority_end; } decoded->path = unescape_string (hier_part_start, hier_part_end, "/"); if (decoded->path == NULL) { _g_decoded_uri_free (decoded); return NULL; } return decoded; } static gboolean is_valid (char c, const char *reserved_chars_allowed) { if (g_ascii_isalnum (c) || c == '-' || c == '.' || c == '_' || c == '~') return TRUE; if (reserved_chars_allowed && strchr (reserved_chars_allowed, c) != NULL) return TRUE; return FALSE; } static void g_string_append_encoded (GString *string, const char *encoded, const char *reserved_chars_allowed) { unsigned char c; static const gchar hex[] = "0123456789ABCDEF"; while ((c = *encoded) != 0) { if (is_valid (c, reserved_chars_allowed)) { g_string_append_c (string, c); encoded++; } else { g_string_append_c (string, '%'); g_string_append_c (string, hex[((guchar)c) >> 4]); g_string_append_c (string, hex[((guchar)c) & 0xf]); encoded++; } } } static char * _g_encode_uri (GDecodedUri *decoded) { GString *uri; uri = g_string_new (NULL); g_string_append (uri, decoded->scheme); g_string_append (uri, "://"); if (decoded->host != NULL) { if (decoded->userinfo) { /* userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) */ g_string_append_encoded (uri, decoded->userinfo, SUB_DELIM_CHARS ":"); g_string_append_c (uri, '@'); } g_string_append (uri, decoded->host); if (decoded->port != -1) { g_string_append_c (uri, ':'); g_string_append_printf (uri, "%d", decoded->port); } } g_string_append_encoded (uri, decoded->path, SUB_DELIM_CHARS ":@/"); if (decoded->query) { g_string_append_c (uri, '?'); g_string_append (uri, decoded->query); } if (decoded->fragment) { g_string_append_c (uri, '#'); g_string_append (uri, decoded->fragment); } return g_string_free (uri, FALSE); }