| /* 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 <http://www.gnu.org/licenses/>. | |
| * | |
| * Author: Alexander Larsson <alexl@redhat.com> | |
| */ | |
| #include "config.h" | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stdlib.h> | |
| #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); | |
| } |