| /* GStreamer |
| * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu> |
| * 2000 Wim Taymans <wtay@chello.be> |
| * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net> |
| * Copyright (C) 2014 David Waring, British Broadcasting Corporation |
| * <david.waring@rd.bbc.co.uk> |
| * |
| * gsturi.c: register URI handlers and IETF RFC 3986 URI manipulations. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| /** |
| * SECTION:gsturihandler |
| * @short_description: Interface to ease URI handling in plugins. |
| * |
| * The URIHandler is an interface that is implemented by Source and Sink |
| * #GstElement to simplify then handling of URI. |
| * |
| * An application can use the following functions to quickly get an element |
| * that handles the given URI for reading or writing |
| * (gst_element_make_from_uri()). |
| * |
| * Source and Sink plugins should implement this interface when possible. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include "gst_private.h" |
| #include "gst.h" |
| #include "gsturi.h" |
| #include "gstinfo.h" |
| #include "gstregistry.h" |
| |
| #include "gst-i18n-lib.h" |
| |
| #include <string.h> |
| #include <glib.h> |
| #include <glib/gprintf.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_uri_handler_debug); |
| #define GST_CAT_DEFAULT gst_uri_handler_debug |
| |
| #ifndef HAVE_STRCASESTR |
| #define strcasestr _gst_ascii_strcasestr |
| |
| /* From https://github.com/freebsd/freebsd/blob/master/contrib/file/src/strcasestr.c |
| * Updated to use GLib types and GLib string functions |
| * |
| * Copyright (c) 1990, 1993 |
| * The Regents of the University of California. All rights reserved. |
| * |
| * This code is derived from software contributed to Berkeley by |
| * Chris Torek. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| /* |
| * Find the first occurrence of find in s, ignore case. |
| */ |
| |
| static gchar * |
| _gst_ascii_strcasestr (const gchar * s, const gchar * find) |
| { |
| gchar c, sc; |
| gsize len; |
| |
| if ((c = *find++) != 0) { |
| c = g_ascii_tolower (c); |
| len = strlen (find); |
| do { |
| do { |
| if ((sc = *s++) == 0) |
| return (NULL); |
| } while (g_ascii_tolower (sc) != c); |
| } while (g_ascii_strncasecmp (s, find, len) != 0); |
| s--; |
| } |
| return (gchar *) (gintptr) (s); |
| } |
| #endif |
| |
| #if !GLIB_CHECK_VERSION (2, 33, 4) |
| #define g_list_copy_deep gst_g_list_copy_deep |
| static GList * |
| gst_g_list_copy_deep (GList * list, GCopyFunc func, gpointer user_data) |
| { |
| list = g_list_copy (list); |
| |
| if (func != NULL) { |
| GList *l; |
| |
| for (l = list; l != NULL; l = l->next) { |
| l->data = func (l->data, user_data); |
| } |
| } |
| |
| return list; |
| } |
| #endif |
| |
| GType |
| gst_uri_handler_get_type (void) |
| { |
| static volatile gsize urihandler_type = 0; |
| |
| if (g_once_init_enter (&urihandler_type)) { |
| GType _type; |
| static const GTypeInfo urihandler_info = { |
| sizeof (GstURIHandlerInterface), |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| 0, |
| 0, |
| NULL, |
| NULL |
| }; |
| |
| _type = g_type_register_static (G_TYPE_INTERFACE, |
| "GstURIHandler", &urihandler_info, 0); |
| |
| GST_DEBUG_CATEGORY_INIT (gst_uri_handler_debug, "GST_URI", GST_DEBUG_BOLD, |
| "handling of URIs"); |
| g_once_init_leave (&urihandler_type, _type); |
| } |
| return urihandler_type; |
| } |
| |
| GQuark |
| gst_uri_error_quark (void) |
| { |
| return g_quark_from_static_string ("gst-uri-error-quark"); |
| } |
| |
| static const guchar acceptable[96] = { /* X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF */ |
| 0x00, 0x3F, 0x20, 0x20, 0x20, 0x00, 0x2C, 0x3F, 0x3F, 0x3F, 0x3F, 0x22, 0x20, 0x3F, 0x3F, 0x1C, /* 2X !"#$%&'()*+,-./ */ |
| 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x38, 0x20, 0x20, 0x2C, 0x20, 0x2C, /* 3X 0123456789:;<=>? */ |
| 0x30, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 4X @ABCDEFGHIJKLMNO */ |
| 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x3F, /* 5X PQRSTUVWXYZ[\]^_ */ |
| 0x20, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 6X `abcdefghijklmno */ |
| 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x3F, 0x20 /* 7X pqrstuvwxyz{|}~DEL */ |
| }; |
| |
| typedef enum |
| { |
| UNSAFE_ALL = 0x1, /* Escape all unsafe characters */ |
| UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+' */ |
| UNSAFE_PATH = 0x4, /* Allows '/' and '?' and '&' and '=' */ |
| UNSAFE_DOS_PATH = 0x8, /* Allows '/' and '?' and '&' and '=' and ':' */ |
| UNSAFE_HOST = 0x10, /* Allows '/' and ':' and '@' */ |
| UNSAFE_SLASHES = 0x20 /* Allows all characters except for '/' and '%' */ |
| } UnsafeCharacterSet; |
| |
| #define HEX_ESCAPE '%' |
| |
| /* Escape undesirable characters using % |
| * ------------------------------------- |
| * |
| * This function takes a pointer to a string in which |
| * some characters may be unacceptable unescaped. |
| * It returns a string which has these characters |
| * represented by a '%' character followed by two hex digits. |
| * |
| * This routine returns a g_malloced string. |
| */ |
| |
| static const gchar hex[16] = "0123456789ABCDEF"; |
| |
| static gchar * |
| escape_string_internal (const gchar * string, UnsafeCharacterSet mask) |
| { |
| #define ACCEPTABLE_CHAR(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask)) |
| |
| const gchar *p; |
| gchar *q; |
| gchar *result; |
| guchar c; |
| gint unacceptable; |
| UnsafeCharacterSet use_mask; |
| |
| g_return_val_if_fail (mask == UNSAFE_ALL |
| || mask == UNSAFE_ALLOW_PLUS |
| || mask == UNSAFE_PATH |
| || mask == UNSAFE_DOS_PATH |
| || mask == UNSAFE_HOST || mask == UNSAFE_SLASHES, NULL); |
| |
| if (string == NULL) { |
| return NULL; |
| } |
| |
| unacceptable = 0; |
| use_mask = mask; |
| for (p = string; *p != '\0'; p++) { |
| c = *p; |
| if (!ACCEPTABLE_CHAR (c)) { |
| unacceptable++; |
| } |
| if ((use_mask == UNSAFE_HOST) && (unacceptable || (c == '/'))) { |
| /* when escaping a host, if we hit something that needs to be escaped, or we finally |
| * hit a path separator, revert to path mode (the host segment of the url is over). |
| */ |
| use_mask = UNSAFE_PATH; |
| } |
| } |
| |
| result = g_malloc (p - string + unacceptable * 2 + 1); |
| |
| use_mask = mask; |
| for (q = result, p = string; *p != '\0'; p++) { |
| c = *p; |
| |
| if (!ACCEPTABLE_CHAR (c)) { |
| *q++ = HEX_ESCAPE; /* means hex coming */ |
| *q++ = hex[c >> 4]; |
| *q++ = hex[c & 15]; |
| } else { |
| *q++ = c; |
| } |
| if ((use_mask == UNSAFE_HOST) && (!ACCEPTABLE_CHAR (c) || (c == '/'))) { |
| use_mask = UNSAFE_PATH; |
| } |
| } |
| |
| *q = '\0'; |
| |
| return result; |
| } |
| |
| /* escape_string: |
| * @string: string to be escaped |
| * |
| * Escapes @string, replacing any and all special characters |
| * with equivalent escape sequences. |
| * |
| * Return value: a newly allocated string equivalent to @string |
| * but with all special characters escaped |
| **/ |
| static gchar * |
| escape_string (const gchar * string) |
| { |
| return escape_string_internal (string, UNSAFE_ALL); |
| } |
| |
| static int |
| hex_to_int (gchar c) |
| { |
| return c >= '0' && c <= '9' ? c - '0' |
| : c >= 'A' && c <= 'F' ? c - 'A' + 10 |
| : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; |
| } |
| |
| static int |
| unescape_character (const char *scanner) |
| { |
| int first_digit; |
| int second_digit; |
| |
| first_digit = hex_to_int (*scanner++); |
| if (first_digit < 0) { |
| return -1; |
| } |
| |
| second_digit = hex_to_int (*scanner); |
| if (second_digit < 0) { |
| return -1; |
| } |
| |
| return (first_digit << 4) | second_digit; |
| } |
| |
| /* unescape_string: |
| * @escaped_string: an escaped URI, path, or other string |
| * @illegal_characters: a string containing a sequence of characters |
| * considered "illegal", '\0' is automatically in this list. |
| * |
| * Decodes escaped characters (i.e. PERCENTxx sequences) in @escaped_string. |
| * Characters are encoded in PERCENTxy form, where xy is the ASCII hex code |
| * for character 16x+y. |
| * |
| * Return value: (nullable): a newly allocated string with the |
| * unescaped equivalents, or %NULL if @escaped_string contained one of |
| * the characters in @illegal_characters. |
| **/ |
| static char * |
| unescape_string (const gchar * escaped_string, const gchar * illegal_characters) |
| { |
| const gchar *in; |
| gchar *out, *result; |
| gint character; |
| |
| if (escaped_string == NULL) { |
| return NULL; |
| } |
| |
| result = g_malloc (strlen (escaped_string) + 1); |
| |
| out = result; |
| for (in = escaped_string; *in != '\0'; in++) { |
| character = *in; |
| if (*in == HEX_ESCAPE) { |
| character = unescape_character (in + 1); |
| |
| /* 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 += 2; |
| } |
| *out++ = (char) character; |
| } |
| |
| *out = '\0'; |
| g_assert ((gsize) (out - result) <= strlen (escaped_string)); |
| return result; |
| |
| } |
| |
| |
| static void |
| gst_uri_protocol_check_internal (const gchar * uri, gchar ** endptr) |
| { |
| gchar *check = (gchar *) uri; |
| |
| g_assert (uri != NULL); |
| g_assert (endptr != NULL); |
| |
| if (g_ascii_isalpha (*check)) { |
| check++; |
| while (g_ascii_isalnum (*check) || *check == '+' |
| || *check == '-' || *check == '.') |
| check++; |
| } |
| |
| *endptr = check; |
| } |
| |
| /** |
| * gst_uri_protocol_is_valid: |
| * @protocol: A string |
| * |
| * Tests if the given string is a valid protocol identifier. Protocols |
| * must consist of alphanumeric characters, '+', '-' and '.' and must |
| * start with a alphabetic character. See RFC 3986 Section 3.1. |
| * |
| * Returns: %TRUE if the string is a valid protocol identifier, %FALSE otherwise. |
| */ |
| gboolean |
| gst_uri_protocol_is_valid (const gchar * protocol) |
| { |
| gchar *endptr; |
| |
| g_return_val_if_fail (protocol != NULL, FALSE); |
| |
| gst_uri_protocol_check_internal (protocol, &endptr); |
| |
| return *endptr == '\0' && ((gsize) (endptr - protocol)) >= 2; |
| } |
| |
| /** |
| * gst_uri_is_valid: |
| * @uri: A URI string |
| * |
| * Tests if the given string is a valid URI identifier. URIs start with a valid |
| * scheme followed by ":" and maybe a string identifying the location. |
| * |
| * Returns: %TRUE if the string is a valid URI |
| */ |
| gboolean |
| gst_uri_is_valid (const gchar * uri) |
| { |
| gchar *endptr; |
| |
| g_return_val_if_fail (uri != NULL, FALSE); |
| |
| gst_uri_protocol_check_internal (uri, &endptr); |
| |
| return *endptr == ':' && ((gsize) (endptr - uri)) >= 2; |
| } |
| |
| /** |
| * gst_uri_get_protocol: |
| * @uri: A URI string |
| * |
| * Extracts the protocol out of a given valid URI. The returned string must be |
| * freed using g_free(). |
| * |
| * Returns: The protocol for this URI. |
| */ |
| gchar * |
| gst_uri_get_protocol (const gchar * uri) |
| { |
| gchar *colon; |
| |
| g_return_val_if_fail (uri != NULL, NULL); |
| g_return_val_if_fail (gst_uri_is_valid (uri), NULL); |
| |
| colon = strstr (uri, ":"); |
| |
| return g_ascii_strdown (uri, colon - uri); |
| } |
| |
| /** |
| * gst_uri_has_protocol: |
| * @uri: a URI string |
| * @protocol: a protocol string (e.g. "http") |
| * |
| * Checks if the protocol of a given valid URI matches @protocol. |
| * |
| * Returns: %TRUE if the protocol matches. |
| */ |
| gboolean |
| gst_uri_has_protocol (const gchar * uri, const gchar * protocol) |
| { |
| gchar *colon; |
| |
| g_return_val_if_fail (uri != NULL, FALSE); |
| g_return_val_if_fail (protocol != NULL, FALSE); |
| g_return_val_if_fail (gst_uri_is_valid (uri), FALSE); |
| |
| colon = strstr (uri, ":"); |
| |
| if (colon == NULL) |
| return FALSE; |
| |
| return (g_ascii_strncasecmp (uri, protocol, (gsize) (colon - uri)) == 0); |
| } |
| |
| /** |
| * gst_uri_get_location: |
| * @uri: A URI string |
| * |
| * Extracts the location out of a given valid URI, ie. the protocol and "://" |
| * are stripped from the URI, which means that the location returned includes |
| * the hostname if one is specified. The returned string must be freed using |
| * g_free(). |
| * |
| * Free-function: g_free |
| * |
| * Returns: (transfer full): the location for this URI. Returns %NULL if the |
| * URI isn't valid. If the URI does not contain a location, an empty |
| * string is returned. |
| */ |
| gchar * |
| gst_uri_get_location (const gchar * uri) |
| { |
| const gchar *colon; |
| gchar *unescaped = NULL; |
| |
| g_return_val_if_fail (uri != NULL, NULL); |
| g_return_val_if_fail (gst_uri_is_valid (uri), NULL); |
| |
| colon = strstr (uri, "://"); |
| if (!colon) |
| return NULL; |
| |
| unescaped = unescape_string (colon + 3, "/"); |
| |
| /* On Windows an URI might look like file:///c:/foo/bar.txt or |
| * file:///c|/foo/bar.txt (some Netscape versions) and we want to |
| * return c:/foo/bar.txt as location rather than /c:/foo/bar.txt. |
| * Can't use g_filename_from_uri() here because it will only handle the |
| * file:// protocol */ |
| #ifdef G_OS_WIN32 |
| if (unescaped != NULL && unescaped[0] == '/' && |
| g_ascii_isalpha (unescaped[1]) && |
| (unescaped[2] == ':' || unescaped[2] == '|')) { |
| unescaped[2] = ':'; |
| memmove (unescaped, unescaped + 1, strlen (unescaped + 1) + 1); |
| } |
| #endif |
| |
| GST_LOG ("extracted location '%s' from URI '%s'", GST_STR_NULL (unescaped), |
| uri); |
| return unescaped; |
| } |
| |
| /** |
| * gst_uri_construct: |
| * @protocol: Protocol for URI |
| * @location: (transfer none): Location for URI |
| * |
| * Constructs a URI for a given valid protocol and location. |
| * |
| * Free-function: g_free |
| * |
| * Returns: (transfer full): a new string for this URI. Returns %NULL if the |
| * given URI protocol is not valid, or the given location is %NULL. |
| */ |
| gchar * |
| gst_uri_construct (const gchar * protocol, const gchar * location) |
| { |
| char *escaped, *proto_lowercase; |
| char *retval; |
| |
| g_return_val_if_fail (gst_uri_protocol_is_valid (protocol), NULL); |
| g_return_val_if_fail (location != NULL, NULL); |
| |
| proto_lowercase = g_ascii_strdown (protocol, -1); |
| escaped = escape_string (location); |
| retval = g_strdup_printf ("%s://%s", proto_lowercase, escaped); |
| g_free (escaped); |
| g_free (proto_lowercase); |
| |
| return retval; |
| } |
| |
| typedef struct |
| { |
| GstURIType type; |
| const gchar *protocol; |
| } |
| SearchEntry; |
| |
| static gboolean |
| search_by_entry (GstPluginFeature * feature, gpointer search_entry) |
| { |
| const gchar *const *protocols; |
| GstElementFactory *factory; |
| SearchEntry *entry = (SearchEntry *) search_entry; |
| |
| if (!GST_IS_ELEMENT_FACTORY (feature)) |
| return FALSE; |
| factory = GST_ELEMENT_FACTORY_CAST (feature); |
| |
| if (factory->uri_type != entry->type) |
| return FALSE; |
| |
| protocols = gst_element_factory_get_uri_protocols (factory); |
| |
| if (protocols == NULL) { |
| g_warning ("Factory '%s' implements GstUriHandler interface but returned " |
| "no supported protocols!", gst_plugin_feature_get_name (feature)); |
| return FALSE; |
| } |
| |
| while (*protocols != NULL) { |
| if (g_ascii_strcasecmp (*protocols, entry->protocol) == 0) |
| return TRUE; |
| protocols++; |
| } |
| return FALSE; |
| } |
| |
| static gint |
| sort_by_rank (GstPluginFeature * first, GstPluginFeature * second) |
| { |
| return gst_plugin_feature_get_rank (second) - |
| gst_plugin_feature_get_rank (first); |
| } |
| |
| static GList * |
| get_element_factories_from_uri_protocol (const GstURIType type, |
| const gchar * protocol) |
| { |
| GList *possibilities; |
| SearchEntry entry; |
| |
| g_return_val_if_fail (protocol, NULL); |
| |
| entry.type = type; |
| entry.protocol = protocol; |
| possibilities = gst_registry_feature_filter (gst_registry_get (), |
| search_by_entry, FALSE, &entry); |
| |
| return possibilities; |
| } |
| |
| /** |
| * gst_uri_protocol_is_supported: |
| * @type: Whether to check for a source or a sink |
| * @protocol: Protocol that should be checked for (e.g. "http" or "smb") |
| * |
| * Checks if an element exists that supports the given URI protocol. Note |
| * that a positive return value does not imply that a subsequent call to |
| * gst_element_make_from_uri() is guaranteed to work. |
| * |
| * Returns: %TRUE |
| */ |
| gboolean |
| gst_uri_protocol_is_supported (const GstURIType type, const gchar * protocol) |
| { |
| GList *possibilities; |
| |
| g_return_val_if_fail (protocol, FALSE); |
| |
| possibilities = get_element_factories_from_uri_protocol (type, protocol); |
| |
| if (possibilities) { |
| g_list_free (possibilities); |
| return TRUE; |
| } else |
| return FALSE; |
| } |
| |
| /** |
| * gst_element_make_from_uri: |
| * @type: Whether to create a source or a sink |
| * @uri: URI to create an element for |
| * @elementname: (allow-none): Name of created element, can be %NULL. |
| * @error: (allow-none): address where to store error information, or %NULL. |
| * |
| * Creates an element for handling the given URI. |
| * |
| * Returns: (transfer floating): a new element or %NULL if none could be created |
| */ |
| GstElement * |
| gst_element_make_from_uri (const GstURIType type, const gchar * uri, |
| const gchar * elementname, GError ** error) |
| { |
| GList *possibilities, *walk; |
| gchar *protocol; |
| GstElement *ret = NULL; |
| |
| g_return_val_if_fail (gst_is_initialized (), NULL); |
| g_return_val_if_fail (GST_URI_TYPE_IS_VALID (type), NULL); |
| g_return_val_if_fail (gst_uri_is_valid (uri), NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| GST_DEBUG ("type:%d, uri:%s, elementname:%s", type, uri, elementname); |
| |
| protocol = gst_uri_get_protocol (uri); |
| possibilities = get_element_factories_from_uri_protocol (type, protocol); |
| |
| if (!possibilities) { |
| GST_DEBUG ("No %s for URI '%s'", type == GST_URI_SINK ? "sink" : "source", |
| uri); |
| /* The error message isn't great, but we don't expect applications to |
| * show that error to users, but call the missing plugins functions */ |
| g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL, |
| _("No URI handler for the %s protocol found"), protocol); |
| g_free (protocol); |
| return NULL; |
| } |
| g_free (protocol); |
| |
| possibilities = g_list_sort (possibilities, (GCompareFunc) sort_by_rank); |
| walk = possibilities; |
| while (walk) { |
| GstElementFactory *factory = walk->data; |
| GError *uri_err = NULL; |
| |
| ret = gst_element_factory_create (factory, elementname); |
| if (ret != NULL) { |
| GstURIHandler *handler = GST_URI_HANDLER (ret); |
| |
| if (gst_uri_handler_set_uri (handler, uri, &uri_err)) |
| break; |
| |
| GST_WARNING ("%s didn't accept URI '%s': %s", GST_OBJECT_NAME (ret), uri, |
| uri_err->message); |
| |
| if (error != NULL && *error == NULL) |
| g_propagate_error (error, uri_err); |
| else |
| g_error_free (uri_err); |
| |
| gst_object_unref (ret); |
| ret = NULL; |
| } |
| walk = walk->next; |
| } |
| gst_plugin_feature_list_free (possibilities); |
| |
| GST_LOG_OBJECT (ret, "created %s for URL '%s'", |
| type == GST_URI_SINK ? "sink" : "source", uri); |
| |
| /* if the first handler didn't work, but we found another one that works */ |
| if (ret != NULL) |
| g_clear_error (error); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_uri_handler_get_uri_type: |
| * @handler: A #GstURIHandler. |
| * |
| * Gets the type of the given URI handler |
| * |
| * Returns: the #GstURIType of the URI handler. |
| * Returns #GST_URI_UNKNOWN if the @handler isn't implemented correctly. |
| */ |
| GstURIType |
| gst_uri_handler_get_uri_type (GstURIHandler * handler) |
| { |
| GstURIHandlerInterface *iface; |
| GstURIType ret; |
| |
| g_return_val_if_fail (GST_IS_URI_HANDLER (handler), GST_URI_UNKNOWN); |
| |
| iface = GST_URI_HANDLER_GET_INTERFACE (handler); |
| g_return_val_if_fail (iface != NULL, GST_URI_UNKNOWN); |
| g_return_val_if_fail (iface->get_type != NULL, GST_URI_UNKNOWN); |
| |
| ret = iface->get_type (G_OBJECT_TYPE (handler)); |
| g_return_val_if_fail (GST_URI_TYPE_IS_VALID (ret), GST_URI_UNKNOWN); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_uri_handler_get_protocols: |
| * @handler: A #GstURIHandler. |
| * |
| * Gets the list of protocols supported by @handler. This list may not be |
| * modified. |
| * |
| * Returns: (transfer none) (element-type utf8) (nullable): the |
| * supported protocols. Returns %NULL if the @handler isn't |
| * implemented properly, or the @handler doesn't support any |
| * protocols. |
| */ |
| const gchar *const * |
| gst_uri_handler_get_protocols (GstURIHandler * handler) |
| { |
| GstURIHandlerInterface *iface; |
| const gchar *const *ret; |
| |
| g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL); |
| |
| iface = GST_URI_HANDLER_GET_INTERFACE (handler); |
| g_return_val_if_fail (iface != NULL, NULL); |
| g_return_val_if_fail (iface->get_protocols != NULL, NULL); |
| |
| ret = iface->get_protocols (G_OBJECT_TYPE (handler)); |
| g_return_val_if_fail (ret != NULL, NULL); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_uri_handler_get_uri: |
| * @handler: A #GstURIHandler |
| * |
| * Gets the currently handled URI. |
| * |
| * Returns: (transfer full) (nullable): the URI currently handled by |
| * the @handler. Returns %NULL if there are no URI currently |
| * handled. The returned string must be freed with g_free() when no |
| * longer needed. |
| */ |
| gchar * |
| gst_uri_handler_get_uri (GstURIHandler * handler) |
| { |
| GstURIHandlerInterface *iface; |
| gchar *ret; |
| |
| g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL); |
| |
| iface = GST_URI_HANDLER_GET_INTERFACE (handler); |
| g_return_val_if_fail (iface != NULL, NULL); |
| g_return_val_if_fail (iface->get_uri != NULL, NULL); |
| ret = iface->get_uri (handler); |
| if (ret != NULL) |
| g_return_val_if_fail (gst_uri_is_valid (ret), NULL); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_uri_handler_set_uri: |
| * @handler: A #GstURIHandler |
| * @uri: URI to set |
| * @error: (allow-none): address where to store a #GError in case of |
| * an error, or %NULL |
| * |
| * Tries to set the URI of the given handler. |
| * |
| * Returns: %TRUE if the URI was set successfully, else %FALSE. |
| */ |
| gboolean |
| gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri, |
| GError ** error) |
| { |
| GstURIHandlerInterface *iface; |
| gboolean ret; |
| gchar *protocol; |
| |
| g_return_val_if_fail (GST_IS_URI_HANDLER (handler), FALSE); |
| g_return_val_if_fail (gst_uri_is_valid (uri), FALSE); |
| g_return_val_if_fail (error == NULL || *error == NULL, FALSE); |
| |
| iface = GST_URI_HANDLER_GET_INTERFACE (handler); |
| g_return_val_if_fail (iface != NULL, FALSE); |
| g_return_val_if_fail (iface->set_uri != NULL, FALSE); |
| |
| protocol = gst_uri_get_protocol (uri); |
| |
| if (iface->get_protocols) { |
| const gchar *const *protocols; |
| const gchar *const *p; |
| gboolean found_protocol = FALSE; |
| |
| protocols = iface->get_protocols (G_OBJECT_TYPE (handler)); |
| if (protocols != NULL) { |
| for (p = protocols; *p != NULL; ++p) { |
| if (g_ascii_strcasecmp (protocol, *p) == 0) { |
| found_protocol = TRUE; |
| break; |
| } |
| } |
| |
| if (!found_protocol) { |
| g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL, |
| _("URI scheme '%s' not supported"), protocol); |
| g_free (protocol); |
| return FALSE; |
| } |
| } |
| } |
| |
| ret = iface->set_uri (handler, uri, error); |
| |
| g_free (protocol); |
| |
| return ret; |
| } |
| |
| static gchar * |
| gst_file_utils_canonicalise_path (const gchar * path) |
| { |
| gchar **parts, **p, *clean_path; |
| |
| #ifdef G_OS_WIN32 |
| { |
| GST_WARNING ("FIXME: canonicalise win32 path"); |
| return g_strdup (path); |
| } |
| #endif |
| |
| parts = g_strsplit (path, "/", -1); |
| |
| p = parts; |
| while (*p != NULL) { |
| if (strcmp (*p, ".") == 0) { |
| /* just move all following parts on top of this, incl. NUL terminator */ |
| g_free (*p); |
| memmove (p, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *)); |
| /* re-check the new current part again in the next iteration */ |
| continue; |
| } else if (strcmp (*p, "..") == 0 && p > parts) { |
| /* just move all following parts on top of the previous part, incl. |
| * NUL terminator */ |
| g_free (*(p - 1)); |
| g_free (*p); |
| memmove (p - 1, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *)); |
| /* re-check the new current part again in the next iteration */ |
| --p; |
| continue; |
| } |
| ++p; |
| } |
| if (*path == '/') { |
| guint num_parts; |
| |
| num_parts = g_strv_length (parts) + 1; /* incl. terminator */ |
| parts = g_renew (gchar *, parts, num_parts + 1); |
| memmove (parts + 1, parts, num_parts * sizeof (gchar *)); |
| parts[0] = g_strdup ("/"); |
| } |
| |
| clean_path = g_build_filenamev (parts); |
| g_strfreev (parts); |
| return clean_path; |
| } |
| |
| static gboolean |
| file_path_contains_relatives (const gchar * path) |
| { |
| return (strstr (path, "/./") != NULL || strstr (path, "/../") != NULL || |
| strstr (path, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) != NULL || |
| strstr (path, G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) != NULL); |
| } |
| |
| /** |
| * gst_filename_to_uri: |
| * @filename: absolute or relative file name path |
| * @error: pointer to error, or %NULL |
| * |
| * Similar to g_filename_to_uri(), but attempts to handle relative file paths |
| * as well. Before converting @filename into an URI, it will be prefixed by |
| * the current working directory if it is a relative path, and then the path |
| * will be canonicalised so that it doesn't contain any './' or '../' segments. |
| * |
| * On Windows #filename should be in UTF-8 encoding. |
| * |
| * Returns: newly-allocated URI string, or NULL on error. The caller must |
| * free the URI string with g_free() when no longer needed. |
| */ |
| gchar * |
| gst_filename_to_uri (const gchar * filename, GError ** error) |
| { |
| gchar *abs_location = NULL; |
| gchar *uri, *abs_clean; |
| |
| g_return_val_if_fail (filename != NULL, NULL); |
| g_return_val_if_fail (error == NULL || *error == NULL, NULL); |
| |
| if (g_path_is_absolute (filename)) { |
| if (!file_path_contains_relatives (filename)) { |
| uri = g_filename_to_uri (filename, NULL, error); |
| goto beach; |
| } |
| |
| abs_location = g_strdup (filename); |
| } else { |
| gchar *cwd; |
| |
| cwd = g_get_current_dir (); |
| abs_location = g_build_filename (cwd, filename, NULL); |
| g_free (cwd); |
| |
| if (!file_path_contains_relatives (abs_location)) { |
| uri = g_filename_to_uri (abs_location, NULL, error); |
| goto beach; |
| } |
| } |
| |
| /* path is now absolute, but contains '.' or '..' */ |
| abs_clean = gst_file_utils_canonicalise_path (abs_location); |
| GST_LOG ("'%s' -> '%s' -> '%s'", filename, abs_location, abs_clean); |
| uri = g_filename_to_uri (abs_clean, NULL, error); |
| g_free (abs_clean); |
| |
| beach: |
| |
| g_free (abs_location); |
| GST_DEBUG ("'%s' -> '%s'", filename, uri); |
| return uri; |
| } |
| |
| /**************************************************************************** |
| * GstUri - GstMiniObject to parse and merge URIs according to IETF RFC 3986 |
| ****************************************************************************/ |
| |
| /** |
| * SECTION:gsturi |
| * @short_description: URI parsing and manipulation. |
| * |
| * A #GstUri object can be used to parse and split a URI string into its |
| * constituant parts. Two #GstUri objects can be joined to make a new #GstUri |
| * using the algorithm described in RFC3986. |
| */ |
| |
| /* Definition for GstUri object */ |
| struct _GstUri |
| { |
| /*< private > */ |
| GstMiniObject mini_object; |
| gchar *scheme; |
| gchar *userinfo; |
| gchar *host; |
| guint port; |
| GList *path; |
| GHashTable *query; |
| gchar *fragment; |
| }; |
| |
| GST_DEFINE_MINI_OBJECT_TYPE (GstUri, gst_uri); |
| |
| static GstUri *_gst_uri_copy (const GstUri * uri); |
| static void _gst_uri_free (GstUri * uri); |
| static GstUri *_gst_uri_new (void); |
| static GList *_remove_dot_segments (GList * path); |
| |
| /* private GstUri functions */ |
| |
| static GstUri * |
| _gst_uri_new (void) |
| { |
| GstUri *uri; |
| uri = GST_URI_CAST (g_slice_new0 (GstUri)); |
| |
| if (uri) |
| gst_mini_object_init (GST_MINI_OBJECT_CAST (uri), 0, gst_uri_get_type (), |
| (GstMiniObjectCopyFunction) _gst_uri_copy, NULL, |
| (GstMiniObjectFreeFunction) _gst_uri_free); |
| |
| return uri; |
| } |
| |
| static void |
| _gst_uri_free (GstUri * uri) |
| { |
| g_return_if_fail (GST_IS_URI (uri)); |
| |
| g_free (uri->scheme); |
| g_free (uri->userinfo); |
| g_free (uri->host); |
| g_list_free_full (uri->path, g_free); |
| if (uri->query) |
| g_hash_table_unref (uri->query); |
| g_free (uri->fragment); |
| |
| g_slice_free1 (sizeof (*uri), uri); |
| } |
| |
| static GHashTable * |
| _gst_uri_copy_query_table (GHashTable * orig) |
| { |
| GHashTable *new = NULL; |
| |
| if (orig != NULL) { |
| GHashTableIter iter; |
| gpointer key, value; |
| new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); |
| g_hash_table_iter_init (&iter, orig); |
| while (g_hash_table_iter_next (&iter, &key, &value)) { |
| g_hash_table_insert (new, g_strdup (key), g_strdup (value)); |
| } |
| } |
| |
| return new; |
| } |
| |
| static GstUri * |
| _gst_uri_copy (const GstUri * orig_uri) |
| { |
| GstUri *new_uri; |
| |
| g_return_val_if_fail (GST_IS_URI (orig_uri), NULL); |
| |
| new_uri = _gst_uri_new (); |
| |
| if (new_uri) { |
| new_uri->scheme = g_strdup (orig_uri->scheme); |
| new_uri->userinfo = g_strdup (orig_uri->userinfo); |
| new_uri->host = g_strdup (orig_uri->host); |
| new_uri->port = orig_uri->port; |
| new_uri->path = g_list_copy_deep (orig_uri->path, (GCopyFunc) g_strdup, |
| NULL); |
| new_uri->query = _gst_uri_copy_query_table (orig_uri->query); |
| new_uri->fragment = g_strdup (orig_uri->fragment); |
| } |
| |
| return new_uri; |
| } |
| |
| /* |
| * _gst_uri_compare_lists: |
| * |
| * Compare two lists for equality. This compares the two lists, item for item, |
| * comparing items in the same position in the two lists. If @first is |
| * considered less than @second the result will be negative. If @first is |
| * considered to be more than @second then the result will be positive. If the |
| * lists are considered to be equal then the result will be 0. If two lists |
| * have the same items, but one list is shorter than the other, then the |
| * shorter list is considered to be less than the longer list. |
| */ |
| static gint |
| _gst_uri_compare_lists (GList * first, GList * second, GCompareFunc cmp_fn) |
| { |
| GList *itr1, *itr2; |
| gint result; |
| |
| for (itr1 = first, itr2 = second; |
| itr1 != NULL || itr2 != NULL; itr1 = itr1->next, itr2 = itr2->next) { |
| if (itr1 == NULL) |
| return -1; |
| if (itr2 == NULL) |
| return 1; |
| result = cmp_fn (itr1->data, itr2->data); |
| if (result != 0) |
| return result; |
| } |
| return 0; |
| } |
| |
| typedef enum |
| { |
| _GST_URI_NORMALIZE_LOWERCASE = 1, |
| _GST_URI_NORMALIZE_UPPERCASE = 2 |
| } _GstUriNormalizations; |
| |
| /* |
| * Find the first character that hasn't been normalized according to the @flags. |
| */ |
| static gchar * |
| _gst_uri_first_non_normalized_char (gchar * str, guint flags) |
| { |
| gchar *pos; |
| |
| if (str == NULL) |
| return NULL; |
| |
| for (pos = str; *pos; pos++) { |
| if ((flags & _GST_URI_NORMALIZE_UPPERCASE) && g_ascii_islower (*pos)) |
| return pos; |
| if ((flags & _GST_URI_NORMALIZE_LOWERCASE) && g_ascii_isupper (*pos)) |
| return pos; |
| } |
| return NULL; |
| } |
| |
| static gboolean |
| _gst_uri_normalize_lowercase (gchar * str) |
| { |
| gchar *pos; |
| gboolean ret = FALSE; |
| |
| for (pos = _gst_uri_first_non_normalized_char (str, |
| _GST_URI_NORMALIZE_LOWERCASE); |
| pos != NULL; |
| pos = _gst_uri_first_non_normalized_char (pos + 1, |
| _GST_URI_NORMALIZE_LOWERCASE)) { |
| *pos = g_ascii_tolower (*pos); |
| ret = TRUE; |
| } |
| |
| return ret; |
| } |
| |
| #define _gst_uri_normalize_scheme _gst_uri_normalize_lowercase |
| #define _gst_uri_normalize_hostname _gst_uri_normalize_lowercase |
| |
| static gboolean |
| _gst_uri_normalize_path (GList ** path) |
| { |
| GList *new_path; |
| |
| new_path = _remove_dot_segments (*path); |
| if (_gst_uri_compare_lists (new_path, *path, (GCompareFunc) g_strcmp0) != 0) { |
| g_list_free_full (*path, g_free); |
| *path = new_path; |
| return TRUE; |
| } |
| g_list_free_full (new_path, g_free); |
| |
| return FALSE; |
| } |
| |
| static gboolean |
| _gst_uri_normalize_str_noop (gchar * str) |
| { |
| return FALSE; |
| } |
| |
| static gboolean |
| _gst_uri_normalize_table_noop (GHashTable * table) |
| { |
| return FALSE; |
| } |
| |
| #define _gst_uri_normalize_userinfo _gst_uri_normalize_str_noop |
| #define _gst_uri_normalize_query _gst_uri_normalize_table_noop |
| #define _gst_uri_normalize_fragment _gst_uri_normalize_str_noop |
| |
| /* RFC 3986 functions */ |
| |
| static GList * |
| _merge (GList * base, GList * path) |
| { |
| GList *ret, *path_copy, *last; |
| |
| path_copy = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL); |
| /* if base is NULL make path absolute */ |
| if (base == NULL) { |
| if (path_copy != NULL && path_copy->data != NULL) { |
| path_copy = g_list_prepend (path_copy, NULL); |
| } |
| return path_copy; |
| } |
| |
| ret = g_list_copy_deep (base, (GCopyFunc) g_strdup, NULL); |
| last = g_list_last (ret); |
| ret = g_list_remove_link (ret, last); |
| g_list_free_full (last, g_free); |
| ret = g_list_concat (ret, path_copy); |
| |
| return ret; |
| } |
| |
| static GList * |
| _remove_dot_segments (GList * path) |
| { |
| GList *out, *elem, *next; |
| |
| if (path == NULL) |
| return NULL; |
| |
| out = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL); |
| |
| for (elem = out; elem; elem = next) { |
| next = elem->next; |
| if (elem->data == NULL && elem != out && next != NULL) { |
| out = g_list_delete_link (out, elem); |
| } else if (g_strcmp0 (elem->data, ".") == 0) { |
| g_free (elem->data); |
| out = g_list_delete_link (out, elem); |
| } else if (g_strcmp0 (elem->data, "..") == 0) { |
| GList *prev = g_list_previous (elem); |
| if (prev && (prev != out || prev->data != NULL)) { |
| g_free (prev->data); |
| out = g_list_delete_link (out, prev); |
| } |
| g_free (elem->data); |
| out = g_list_delete_link (out, elem); |
| } |
| } |
| |
| return out; |
| } |
| |
| static gchar * |
| _gst_uri_escape_userinfo (const gchar * userinfo) |
| { |
| return g_uri_escape_string (userinfo, |
| G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, FALSE); |
| } |
| |
| static gchar * |
| _gst_uri_escape_host (const gchar * host) |
| { |
| return g_uri_escape_string (host, |
| G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, FALSE); |
| } |
| |
| static gchar * |
| _gst_uri_escape_host_colon (const gchar * host) |
| { |
| return g_uri_escape_string (host, |
| G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS ":", FALSE); |
| } |
| |
| static gchar * |
| _gst_uri_escape_path_segment (const gchar * segment) |
| { |
| return g_uri_escape_string (segment, |
| G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT, FALSE); |
| } |
| |
| static gchar * |
| _gst_uri_escape_http_query_element (const gchar * element) |
| { |
| gchar *ret, *c; |
| |
| ret = g_uri_escape_string (element, "!$'()*,;:@/? ", FALSE); |
| for (c = ret; *c; c++) |
| if (*c == ' ') |
| *c = '+'; |
| return ret; |
| } |
| |
| static gchar * |
| _gst_uri_escape_fragment (const gchar * fragment) |
| { |
| return g_uri_escape_string (fragment, |
| G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?", FALSE); |
| } |
| |
| static GList * |
| _gst_uri_string_to_list (const gchar * str, const gchar * sep, gboolean convert, |
| gboolean unescape) |
| { |
| GList *new_list = NULL; |
| |
| if (str) { |
| guint pct_sep_len = 0; |
| gchar *pct_sep = NULL; |
| gchar **split_str; |
| |
| if (convert && !unescape) { |
| pct_sep = g_strdup_printf ("%%%2.2X", (guint) (*sep)); |
| pct_sep_len = 3; |
| } |
| |
| split_str = g_strsplit (str, sep, -1); |
| if (split_str) { |
| gchar **next_elem; |
| for (next_elem = split_str; *next_elem; next_elem += 1) { |
| gchar *elem = *next_elem; |
| if (*elem == '\0') { |
| new_list = g_list_append (new_list, NULL); |
| } else { |
| if (convert && !unescape) { |
| gchar *next_sep; |
| for (next_sep = strcasestr (elem, pct_sep); next_sep; |
| next_sep = strcasestr (next_sep + 1, pct_sep)) { |
| *next_sep = *sep; |
| memmove (next_sep + 1, next_sep + pct_sep_len, |
| strlen (next_sep + pct_sep_len) + 1); |
| } |
| } |
| if (unescape) { |
| *next_elem = g_uri_unescape_string (elem, NULL); |
| g_free (elem); |
| elem = *next_elem; |
| } |
| new_list = g_list_append (new_list, g_strdup (elem)); |
| } |
| } |
| } |
| g_strfreev (split_str); |
| if (convert && !unescape) |
| g_free (pct_sep); |
| } |
| |
| return new_list; |
| } |
| |
| static GHashTable * |
| _gst_uri_string_to_table (const gchar * str, const gchar * part_sep, |
| const gchar * kv_sep, gboolean convert, gboolean unescape) |
| { |
| GHashTable *new_table = NULL; |
| |
| if (str) { |
| gchar *pct_part_sep = NULL, *pct_kv_sep = NULL; |
| gchar **split_parts; |
| |
| new_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); |
| |
| if (convert && !unescape) { |
| pct_part_sep = g_strdup_printf ("%%%2.2X", (guint) (*part_sep)); |
| pct_kv_sep = g_strdup_printf ("%%%2.2X", (guint) (*kv_sep)); |
| } |
| |
| split_parts = g_strsplit (str, part_sep, -1); |
| if (split_parts) { |
| gchar **next_part; |
| for (next_part = split_parts; *next_part; next_part += 1) { |
| gchar *part = *next_part; |
| gchar *kv_sep_pos; |
| gchar *key, *value; |
| /* if we are converting percent encoded versions of separators then |
| * substitute the part separator now. */ |
| if (convert && !unescape) { |
| gchar *next_sep; |
| for (next_sep = strcasestr (part, pct_part_sep); next_sep; |
| next_sep = strcasestr (next_sep + 1, pct_part_sep)) { |
| *next_sep = *part_sep; |
| memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1); |
| } |
| } |
| /* find the key/value separator within the part */ |
| kv_sep_pos = g_strstr_len (part, -1, kv_sep); |
| if (kv_sep_pos == NULL) { |
| if (unescape) { |
| key = g_uri_unescape_string (part, NULL); |
| } else { |
| key = g_strdup (part); |
| } |
| value = NULL; |
| } else { |
| if (unescape) { |
| key = g_uri_unescape_segment (part, kv_sep_pos, NULL); |
| value = g_uri_unescape_string (kv_sep_pos + 1, NULL); |
| } else { |
| key = g_strndup (part, kv_sep_pos - part); |
| value = g_strdup (kv_sep_pos + 1); |
| } |
| } |
| /* if we are converting percent encoded versions of separators then |
| * substitute the key/value separator in both key and value now. */ |
| if (convert && !unescape) { |
| gchar *next_sep; |
| for (next_sep = strcasestr (key, pct_kv_sep); next_sep; |
| next_sep = strcasestr (next_sep + 1, pct_kv_sep)) { |
| *next_sep = *kv_sep; |
| memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1); |
| } |
| if (value) { |
| for (next_sep = strcasestr (value, pct_kv_sep); next_sep; |
| next_sep = strcasestr (next_sep + 1, pct_kv_sep)) { |
| *next_sep = *kv_sep; |
| memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1); |
| } |
| } |
| } |
| /* add value to the table */ |
| g_hash_table_insert (new_table, key, value); |
| } |
| } |
| /* tidy up */ |
| g_strfreev (split_parts); |
| if (convert && !unescape) { |
| g_free (pct_part_sep); |
| g_free (pct_kv_sep); |
| } |
| } |
| |
| return new_table; |
| } |
| |
| |
| /* |
| * Method definitions. |
| */ |
| |
| /** |
| * gst_uri_new: |
| * @scheme: (nullable): The scheme for the new URI. |
| * @userinfo: (nullable): The user-info for the new URI. |
| * @host: (nullable): The host name for the new URI. |
| * @port: The port number for the new URI or %GST_URI_NO_PORT. |
| * @path: (nullable): The path for the new URI with '/' separating path |
| * elements. |
| * @query: (nullable): The query string for the new URI with '&' separating |
| * query elements. Elements containing '&' characters |
| * should encode them as "%26". |
| * @fragment: (nullable): The fragment name for the new URI. |
| * |
| * Creates a new #GstUri object with the given URI parts. The path and query |
| * strings will be broken down into their elements. All strings should not be |
| * escaped except where indicated. |
| * |
| * Returns: (transfer full): A new #GstUri object. |
| * |
| * Since: 1.6 |
| */ |
| GstUri * |
| gst_uri_new (const gchar * scheme, const gchar * userinfo, const gchar * host, |
| guint port, const gchar * path, const gchar * query, const gchar * fragment) |
| { |
| GstUri *new_uri; |
| |
| new_uri = _gst_uri_new (); |
| if (new_uri) { |
| new_uri->scheme = g_strdup (scheme); |
| new_uri->userinfo = g_strdup (userinfo); |
| new_uri->host = g_strdup (host); |
| new_uri->port = port; |
| new_uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE); |
| new_uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, FALSE); |
| new_uri->fragment = g_strdup (fragment); |
| } |
| |
| return new_uri; |
| } |
| |
| /** |
| * gst_uri_new_with_base: |
| * @base: (transfer none)(nullable): The base URI to join the new URI to. |
| * @scheme: (nullable): The scheme for the new URI. |
| * @userinfo: (nullable): The user-info for the new URI. |
| * @host: (nullable): The host name for the new URI. |
| * @port: The port number for the new URI or %GST_URI_NO_PORT. |
| * @path: (nullable): The path for the new URI with '/' separating path |
| * elements. |
| * @query: (nullable): The query string for the new URI with '&' separating |
| * query elements. Elements containing '&' characters |
| * should encode them as "%26". |
| * @fragment: (nullable): The fragment name for the new URI. |
| * |
| * Like gst_uri_new(), but joins the new URI onto a base URI. |
| * |
| * Returns: (transfer full): The new URI joined onto @base. |
| * |
| * Since: 1.6 |
| */ |
| GstUri * |
| gst_uri_new_with_base (GstUri * base, const gchar * scheme, |
| const gchar * userinfo, const gchar * host, guint port, const gchar * path, |
| const gchar * query, const gchar * fragment) |
| { |
| GstUri *new_rel_uri; |
| GstUri *new_uri; |
| |
| g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL); |
| |
| new_rel_uri = gst_uri_new (scheme, userinfo, host, port, path, query, |
| fragment); |
| new_uri = gst_uri_join (base, new_rel_uri); |
| gst_uri_unref (new_rel_uri); |
| |
| return new_uri; |
| } |
| |
| /** |
| * gst_uri_from_string: |
| * @uri: The URI string to parse. |
| * |
| * Parses a URI string into a new #GstUri object. Will return NULL if the URI |
| * cannot be parsed. |
| * |
| * Returns: (transfer full)(nullable): A new #GstUri object, or NULL. |
| * |
| * Since: 1.6 |
| */ |
| GstUri * |
| gst_uri_from_string (const gchar * uri) |
| { |
| const gchar *orig_uri = uri; |
| GstUri *uri_obj; |
| |
| uri_obj = _gst_uri_new (); |
| |
| if (uri_obj && uri != NULL) { |
| int i = 0; |
| |
| /* be helpful and skip initial white space */ |
| while (*uri == '\v' || g_ascii_isspace (*uri)) |
| uri++; |
| |
| if (g_ascii_isalpha (uri[i])) { |
| /* find end of scheme name */ |
| i++; |
| while (g_ascii_isalnum (uri[i]) || uri[i] == '+' || uri[i] == '-' || |
| uri[i] == '.') |
| i++; |
| } |
| if (i > 0 && uri[i] == ':') { |
| /* get scheme */ |
| uri_obj->scheme = g_strndup (uri, i); |
| uri += i + 1; |
| } |
| if (uri[0] == '/' && uri[1] == '/') { |
| const gchar *eoa, *eoui, *eoh, *reoh; |
| /* get authority [userinfo@]host[:port] */ |
| uri += 2; |
| /* find end of authority */ |
| eoa = uri + strcspn (uri, "/?#"); |
| |
| /* find end of userinfo */ |
| eoui = strchr (uri, '@'); |
| if (eoui != NULL && eoui < eoa) { |
| uri_obj->userinfo = g_uri_unescape_segment (uri, eoui, NULL); |
| uri = eoui + 1; |
| } |
| /* find end of host */ |
| if (uri[0] == '[') { |
| eoh = strchr (uri, ']'); |
| if (eoh == NULL || eoh > eoa) { |
| GST_DEBUG ("Unable to parse the host part of the URI '%s'.", |
| orig_uri); |
| _gst_uri_free (uri_obj); |
| return NULL; |
| } |
| reoh = eoh + 1; |
| uri++; |
| } else { |
| reoh = eoh = strchr (uri, ':'); |
| if (eoh == NULL || eoh > eoa) |
| reoh = eoh = eoa; |
| } |
| /* don't capture empty host strings */ |
| if (eoh != uri) |
| uri_obj->host = g_uri_unescape_segment (uri, eoh, NULL); |
| |
| uri = reoh; |
| if (uri < eoa) { |
| /* if port number is malformed then we can't parse this */ |
| if (uri[0] != ':' || strspn (uri + 1, "0123456789") != eoa - uri - 1) { |
| GST_DEBUG ("Unable to parse host/port part of the URI '%s'.", |
| orig_uri); |
| _gst_uri_free (uri_obj); |
| return NULL; |
| } |
| /* otherwise treat port as unsigned decimal number */ |
| uri++; |
| while (uri < eoa) { |
| uri_obj->port = uri_obj->port * 10 + g_ascii_digit_value (*uri); |
| uri++; |
| } |
| } |
| uri = eoa; |
| } |
| if (uri != NULL && uri[0] != '\0') { |
| /* get path */ |
| size_t len; |
| len = strcspn (uri, "?#"); |
| if (uri[len] == '\0') { |
| uri_obj->path = _gst_uri_string_to_list (uri, "/", FALSE, TRUE); |
| uri = NULL; |
| } else { |
| if (len > 0) { |
| gchar *path_str = g_strndup (uri, len); |
| uri_obj->path = _gst_uri_string_to_list (path_str, "/", FALSE, TRUE); |
| g_free (path_str); |
| } |
| uri += len; |
| } |
| } |
| if (uri != NULL && uri[0] == '?') { |
| /* get query */ |
| gchar *eoq; |
| eoq = strchr (++uri, '#'); |
| if (eoq == NULL) { |
| uri_obj->query = _gst_uri_string_to_table (uri, "&", "=", TRUE, TRUE); |
| uri = NULL; |
| } else { |
| if (eoq != uri) { |
| gchar *query_str = g_strndup (uri, eoq - uri); |
| uri_obj->query = _gst_uri_string_to_table (query_str, "&", "=", TRUE, |
| TRUE); |
| g_free (query_str); |
| } |
| uri = eoq; |
| } |
| } |
| if (uri != NULL && uri[0] == '#') { |
| uri_obj->fragment = g_uri_unescape_string (uri + 1, NULL); |
| } |
| } |
| |
| return uri_obj; |
| } |
| |
| /** |
| * gst_uri_from_string_with_base: |
| * @base: (transfer none)(nullable): The base URI to join the new URI with. |
| * @uri: The URI string to parse. |
| * |
| * Like gst_uri_from_string() but also joins with a base URI. |
| * |
| * Returns: (transfer full): A new #GstUri object. |
| * |
| * Since: 1.6 |
| */ |
| GstUri * |
| gst_uri_from_string_with_base (GstUri * base, const gchar * uri) |
| { |
| GstUri *new_rel_uri; |
| GstUri *new_uri; |
| |
| g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL); |
| |
| new_rel_uri = gst_uri_from_string (uri); |
| new_uri = gst_uri_join (base, new_rel_uri); |
| gst_uri_unref (new_rel_uri); |
| |
| return new_uri; |
| } |
| |
| /** |
| * gst_uri_equal: |
| * @first: First #GstUri to compare. |
| * @second: Second #GstUri to compare. |
| * |
| * Compares two #GstUri objects to see if they represent the same normalized |
| * URI. |
| * |
| * Returns: %TRUE if the normalized versions of the two URI's would be equal. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_equal (const GstUri * first, const GstUri * second) |
| { |
| gchar *first_norm = NULL, *second_norm = NULL; |
| GList *first_norm_list = NULL, *second_norm_list = NULL; |
| const gchar *first_cmp, *second_cmp; |
| GHashTableIter table_iter; |
| gpointer key, value; |
| int result; |
| |
| g_return_val_if_fail ((first == NULL || GST_IS_URI (first)) && |
| (second == NULL || GST_IS_URI (second)), FALSE); |
| |
| if (first == second) |
| return TRUE; |
| |
| if (first == NULL || second == NULL) |
| return FALSE; |
| |
| if (first->port != second->port) |
| return FALSE; |
| |
| /* work out a version of field value (normalized or not) to compare. |
| * first_cmp, second_cmp will be the values to compare later. |
| * first_norm, second_norm will be non-NULL if normalized versions are used, |
| * and need to be freed later. |
| */ |
| #define GST_URI_NORMALIZED_FIELD(pos, field, norm_fn, flags) \ |
| pos##_cmp = pos->field; \ |
| if (_gst_uri_first_non_normalized_char ((gchar*)pos##_cmp, flags) != NULL) { \ |
| pos##_norm = g_strdup (pos##_cmp); \ |
| norm_fn (pos##_norm); \ |
| pos##_cmp = pos##_norm; \ |
| } |
| |
| /* compare two string values, normalizing if needed */ |
| #define GST_URI_NORMALIZED_CMP_STR(field, norm_fn, flags) \ |
| GST_URI_NORMALIZED_FIELD (first, field, norm_fn, flags) \ |
| GST_URI_NORMALIZED_FIELD (second, field, norm_fn, flags) \ |
| result = g_strcmp0 (first_cmp, second_cmp); \ |
| g_free (first_norm); \ |
| first_norm = NULL; \ |
| g_free (second_norm); \ |
| second_norm = NULL; \ |
| if (result != 0) return FALSE |
| |
| /* compare two string values */ |
| #define GST_URI_CMP_STR(field) \ |
| if (g_strcmp0 (first->field, second->field) != 0) return FALSE |
| |
| /* compare two GLists, normalize lists if needed before comparison */ |
| #define GST_URI_NORMALIZED_CMP_LIST(field, norm_fn, copy_fn, cmp_fn, free_fn) \ |
| first_norm_list = g_list_copy_deep (first->field, (GCopyFunc) copy_fn, NULL); \ |
| norm_fn (&first_norm_list); \ |
| second_norm_list = g_list_copy_deep (second->field, (GCopyFunc) copy_fn, NULL); \ |
| norm_fn (&second_norm_list); \ |
| result = _gst_uri_compare_lists (first_norm_list, second_norm_list, (GCompareFunc) cmp_fn); \ |
| g_list_free_full (first_norm_list, free_fn); \ |
| g_list_free_full (second_norm_list, free_fn); \ |
| if (result != 0) return FALSE |
| |
| GST_URI_CMP_STR (userinfo); |
| |
| GST_URI_CMP_STR (fragment); |
| |
| GST_URI_NORMALIZED_CMP_STR (scheme, _gst_uri_normalize_scheme, |
| _GST_URI_NORMALIZE_LOWERCASE); |
| |
| GST_URI_NORMALIZED_CMP_STR (host, _gst_uri_normalize_hostname, |
| _GST_URI_NORMALIZE_LOWERCASE); |
| |
| GST_URI_NORMALIZED_CMP_LIST (path, _gst_uri_normalize_path, g_strdup, |
| g_strcmp0, g_free); |
| |
| if (first->query == NULL && second->query != NULL) |
| return FALSE; |
| if (first->query != NULL && second->query == NULL) |
| return FALSE; |
| if (first->query != NULL) { |
| if (g_hash_table_size (first->query) != g_hash_table_size (second->query)) |
| return FALSE; |
| |
| g_hash_table_iter_init (&table_iter, first->query); |
| while (g_hash_table_iter_next (&table_iter, &key, &value)) { |
| if (!g_hash_table_contains (second->query, key)) |
| return FALSE; |
| result = g_strcmp0 (g_hash_table_lookup (second->query, key), value); |
| if (result != 0) |
| return FALSE; |
| } |
| } |
| #undef GST_URI_NORMALIZED_CMP_STR |
| #undef GST_URI_CMP_STR |
| #undef GST_URI_NORMALIZED_CMP_LIST |
| #undef GST_URI_NORMALIZED_FIELD |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_join: |
| * @base_uri: (transfer none)(nullable): The base URI to join another to. |
| * @ref_uri: (transfer none)(nullable): The reference URI to join onto the |
| * base URI. |
| * |
| * Join a reference URI onto a base URI using the method from RFC 3986. |
| * If either URI is %NULL then the other URI will be returned with the ref count |
| * increased. |
| * |
| * Returns: (transfer full): A #GstUri which represents the base with the |
| * reference URI joined on. |
| * |
| * Since: 1.6 |
| */ |
| GstUri * |
| gst_uri_join (GstUri * base_uri, GstUri * ref_uri) |
| { |
| const gchar *r_scheme; |
| GstUri *t; |
| |
| g_return_val_if_fail ((base_uri == NULL || GST_IS_URI (base_uri)) && |
| (ref_uri == NULL || GST_IS_URI (ref_uri)), NULL); |
| |
| if (base_uri == NULL && ref_uri == NULL) |
| return NULL; |
| if (base_uri == NULL) { |
| g_return_val_if_fail (GST_IS_URI (ref_uri), NULL); |
| return gst_uri_ref (ref_uri); |
| } |
| if (ref_uri == NULL) { |
| g_return_val_if_fail (GST_IS_URI (base_uri), NULL); |
| return gst_uri_ref (base_uri); |
| } |
| |
| g_return_val_if_fail (GST_IS_URI (base_uri) && GST_IS_URI (ref_uri), NULL); |
| |
| t = _gst_uri_new (); |
| |
| if (t == NULL) |
| return t; |
| |
| /* process according to RFC3986 */ |
| r_scheme = ref_uri->scheme; |
| if (r_scheme != NULL && g_strcmp0 (base_uri->scheme, r_scheme) == 0) { |
| r_scheme = NULL; |
| } |
| if (r_scheme != NULL) { |
| t->scheme = g_strdup (r_scheme); |
| t->userinfo = g_strdup (ref_uri->userinfo); |
| t->host = g_strdup (ref_uri->host); |
| t->port = ref_uri->port; |
| t->path = _remove_dot_segments (ref_uri->path); |
| t->query = _gst_uri_copy_query_table (ref_uri->query); |
| } else { |
| if (ref_uri->host != NULL) { |
| t->userinfo = g_strdup (ref_uri->userinfo); |
| t->host = g_strdup (ref_uri->host); |
| t->port = ref_uri->port; |
| t->path = _remove_dot_segments (ref_uri->path); |
| t->query = _gst_uri_copy_query_table (ref_uri->query); |
| } else { |
| if (ref_uri->path == NULL) { |
| t->path = g_list_copy_deep (base_uri->path, (GCopyFunc) g_strdup, NULL); |
| if (ref_uri->query != NULL) |
| t->query = _gst_uri_copy_query_table (ref_uri->query); |
| else |
| t->query = _gst_uri_copy_query_table (base_uri->query); |
| } else { |
| if (ref_uri->path->data == NULL) |
| t->path = _remove_dot_segments (ref_uri->path); |
| else { |
| GList *mrgd = _merge (base_uri->path, ref_uri->path); |
| t->path = _remove_dot_segments (mrgd); |
| g_list_free_full (mrgd, g_free); |
| } |
| t->query = _gst_uri_copy_query_table (ref_uri->query); |
| } |
| t->userinfo = g_strdup (base_uri->userinfo); |
| t->host = g_strdup (base_uri->host); |
| t->port = base_uri->port; |
| } |
| t->scheme = g_strdup (base_uri->scheme); |
| } |
| t->fragment = g_strdup (ref_uri->fragment); |
| |
| return t; |
| } |
| |
| /** |
| * gst_uri_join_strings: |
| * @base_uri: The percent-encoded base URI. |
| * @ref_uri: The percent-encoded reference URI to join to the @base_uri. |
| * |
| * This is a convenience function to join two URI strings and return the result. |
| * The returned string should be g_free()'d after use. |
| * |
| * Returns: (transfer full): A string representing the percent-encoded join of |
| * the two URIs. |
| * |
| * Since: 1.6 |
| */ |
| gchar * |
| gst_uri_join_strings (const gchar * base_uri, const gchar * ref_uri) |
| { |
| GstUri *base, *result; |
| gchar *result_uri; |
| |
| base = gst_uri_from_string (base_uri); |
| result = gst_uri_from_string_with_base (base, ref_uri); |
| result_uri = gst_uri_to_string (result); |
| gst_uri_unref (base); |
| gst_uri_unref (result); |
| |
| return result_uri; |
| } |
| |
| /** |
| * gst_uri_is_writable: |
| * @uri: The #GstUri object to test. |
| * |
| * Check if it is safe to write to this #GstUri. |
| * |
| * Check if the refcount of @uri is exactly 1, meaning that no other |
| * reference exists to the #GstUri and that the #GstUri is therefore writable. |
| * |
| * Modification of a #GstUri should only be done after verifying that it is |
| * writable. |
| * |
| * Returns: %TRUE if it is safe to write to the object. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_is_writable (const GstUri * uri) |
| { |
| g_return_val_if_fail (GST_IS_URI (uri), FALSE); |
| return gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (uri)); |
| } |
| |
| /** |
| * gst_uri_make_writable: |
| * @uri: (transfer full): The #GstUri object to make writable. |
| * |
| * Make the #GstUri writable. |
| * |
| * Checks if @uri is writable, and if so the original object is returned. If |
| * not, then a writable copy is made and returned. This gives away the |
| * reference to @uri and returns a reference to the new #GstUri. |
| * If @uri is %NULL then %NULL is returned. |
| * |
| * Returns: (transfer full): A writable version of @uri. |
| * |
| * Since: 1.6 |
| */ |
| GstUri * |
| gst_uri_make_writable (GstUri * uri) |
| { |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| return |
| GST_URI_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (uri))); |
| } |
| |
| /** |
| * gst_uri_to_string: |
| * @uri: This #GstUri to convert to a string. |
| * |
| * Convert the URI to a string. |
| * |
| * Returns the URI as held in this object as a gchar* %NUL terminated string. |
| * The caller should g_free() the string once they are finished with it. |
| * The string is put together as described in RFC 3986. |
| * |
| * Returns: (transfer full): The string version of the URI. |
| * |
| * Since: 1.6 |
| */ |
| gchar * |
| gst_uri_to_string (const GstUri * uri) |
| { |
| GString *uri_str; |
| gchar *escaped; |
| |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| |
| uri_str = g_string_new (NULL); |
| |
| if (uri->scheme != NULL) |
| g_string_append_printf (uri_str, "%s:", uri->scheme); |
| |
| if (uri->userinfo != NULL || uri->host != NULL || |
| uri->port != GST_URI_NO_PORT) |
| g_string_append (uri_str, "//"); |
| |
| if (uri->userinfo != NULL) { |
| escaped = _gst_uri_escape_userinfo (uri->userinfo); |
| g_string_append_printf (uri_str, "%s@", escaped); |
| g_free (escaped); |
| } |
| |
| if (uri->host != NULL) { |
| if (strchr (uri->host, ':') != NULL) { |
| escaped = _gst_uri_escape_host_colon (uri->host); |
| g_string_append_printf (uri_str, "[%s]", escaped); |
| g_free (escaped); |
| } else { |
| escaped = _gst_uri_escape_host (uri->host); |
| g_string_append (uri_str, escaped); |
| g_free (escaped); |
| } |
| } |
| |
| if (uri->port != GST_URI_NO_PORT) |
| g_string_append_printf (uri_str, ":%u", uri->port); |
| |
| if (uri->path != NULL) { |
| escaped = gst_uri_get_path_string (uri); |
| g_string_append (uri_str, escaped); |
| g_free (escaped); |
| } |
| |
| if (uri->query) { |
| g_string_append (uri_str, "?"); |
| escaped = gst_uri_get_query_string (uri); |
| g_string_append (uri_str, escaped); |
| g_free (escaped); |
| } |
| |
| if (uri->fragment != NULL) { |
| escaped = _gst_uri_escape_fragment (uri->fragment); |
| g_string_append_printf (uri_str, "#%s", escaped); |
| g_free (escaped); |
| } |
| |
| return g_string_free (uri_str, FALSE); |
| } |
| |
| /** |
| * gst_uri_is_normalized: |
| * @uri: The #GstUri to test to see if it is normalized. |
| * |
| * Tests the @uri to see if it is normalized. A %NULL @uri is considered to be |
| * normalized. |
| * |
| * Returns: TRUE if the URI is normalized or is %NULL. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_is_normalized (const GstUri * uri) |
| { |
| GList *new_path; |
| gboolean ret; |
| |
| if (uri == NULL) |
| return TRUE; |
| |
| g_return_val_if_fail (GST_IS_URI (uri), FALSE); |
| |
| /* check for non-normalized characters in uri parts */ |
| if (_gst_uri_first_non_normalized_char (uri->scheme, |
| _GST_URI_NORMALIZE_LOWERCASE) != NULL || |
| /*_gst_uri_first_non_normalized_char (uri->userinfo, |
| _GST_URI_NORMALIZE_PERCENTAGES) != NULL || */ |
| _gst_uri_first_non_normalized_char (uri->host, |
| _GST_URI_NORMALIZE_LOWERCASE /*| _GST_URI_NORMALIZE_PERCENTAGES */ ) |
| != NULL |
| /*|| _gst_uri_first_non_normalized_char (uri->path, |
| _GST_URI_NORMALIZE_PERCENTAGES) != NULL |
| || _gst_uri_first_non_normalized_char (uri->query, |
| _GST_URI_NORMALIZE_PERCENTAGES) != NULL |
| || _gst_uri_first_non_normalized_char (uri->fragment, |
| _GST_URI_NORMALIZE_PERCENTAGES) != NULL */ ) |
| return FALSE; |
| |
| /* also check path has had dot segments removed */ |
| new_path = _remove_dot_segments (uri->path); |
| ret = |
| (_gst_uri_compare_lists (new_path, uri->path, |
| (GCompareFunc) g_strcmp0) == 0); |
| g_list_free_full (new_path, g_free); |
| return ret; |
| } |
| |
| /** |
| * gst_uri_normalize: |
| * @uri: (transfer none): The #GstUri to normalize. |
| * |
| * Normalization will remove extra path segments ("." and "..") from the URI. It |
| * will also convert the scheme and host name to lower case and any |
| * percent-encoded values to uppercase. |
| * |
| * The #GstUri object must be writable. Check with gst_uri_is_writable() or use |
| * gst_uri_make_writable() first. |
| * |
| * Returns: TRUE if the URI was modified. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_normalize (GstUri * uri) |
| { |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| return _gst_uri_normalize_scheme (uri->scheme) | |
| _gst_uri_normalize_userinfo (uri->userinfo) | |
| _gst_uri_normalize_hostname (uri->host) | |
| _gst_uri_normalize_path (&uri->path) | |
| _gst_uri_normalize_query (uri->query) | |
| _gst_uri_normalize_fragment (uri->fragment); |
| } |
| |
| /** |
| * gst_uri_get_scheme: |
| * @uri: (nullable): This #GstUri object. |
| * |
| * Get the scheme name from the URI or %NULL if it doesn't exist. |
| * If @uri is %NULL then returns %NULL. |
| * |
| * Returns: The scheme from the #GstUri object or %NULL. |
| */ |
| const gchar * |
| gst_uri_get_scheme (const GstUri * uri) |
| { |
| g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); |
| return (uri ? uri->scheme : NULL); |
| } |
| |
| /** |
| * gst_uri_set_scheme: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @scheme: The new scheme to set or %NULL to unset the scheme. |
| * |
| * Set or unset the scheme for the URI. |
| * |
| * Returns: %TRUE if the scheme was set/unset successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_scheme (GstUri * uri, const gchar * scheme) |
| { |
| if (!uri) |
| return scheme == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| g_free (uri->scheme); |
| uri->scheme = g_strdup (scheme); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_userinfo: |
| * @uri: (nullable): This #GstUri object. |
| * |
| * Get the userinfo (usually in the form "username:password") from the URI |
| * or %NULL if it doesn't exist. If @uri is %NULL then returns %NULL. |
| * |
| * Returns: The userinfo from the #GstUri object or %NULL. |
| * |
| * Since: 1.6 |
| */ |
| const gchar * |
| gst_uri_get_userinfo (const GstUri * uri) |
| { |
| g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); |
| return (uri ? uri->userinfo : NULL); |
| } |
| |
| /** |
| * gst_uri_set_userinfo: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @userinfo: The new user-information string to set or %NULL to unset. |
| * |
| * Set or unset the user information for the URI. |
| * |
| * Returns: %TRUE if the user information was set/unset successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_userinfo (GstUri * uri, const gchar * userinfo) |
| { |
| if (!uri) |
| return userinfo == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| g_free (uri->userinfo); |
| uri->userinfo = g_strdup (userinfo); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_host: |
| * @uri: (nullable): This #GstUri object. |
| * |
| * Get the host name from the URI or %NULL if it doesn't exist. |
| * If @uri is %NULL then returns %NULL. |
| * |
| * Returns: The host name from the #GstUri object or %NULL. |
| * |
| * Since: 1.6 |
| */ |
| const gchar * |
| gst_uri_get_host (const GstUri * uri) |
| { |
| g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); |
| return (uri ? uri->host : NULL); |
| } |
| |
| /** |
| * gst_uri_set_host: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @host: The new host string to set or %NULL to unset. |
| * |
| * Set or unset the host for the URI. |
| * |
| * Returns: %TRUE if the host was set/unset successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_host (GstUri * uri, const gchar * host) |
| { |
| if (!uri) |
| return host == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| g_free (uri->host); |
| uri->host = g_strdup (host); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_port: |
| * @uri: (nullable): This #GstUri object. |
| * |
| * Get the port number from the URI or %GST_URI_NO_PORT if it doesn't exist. |
| * If @uri is %NULL then returns %GST_URI_NO_PORT. |
| * |
| * Returns: The port number from the #GstUri object or %GST_URI_NO_PORT. |
| * |
| * Since: 1.6 |
| */ |
| guint |
| gst_uri_get_port (const GstUri * uri) |
| { |
| g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), GST_URI_NO_PORT); |
| return (uri ? uri->port : GST_URI_NO_PORT); |
| } |
| |
| /** |
| * gst_uri_set_port: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @port: The new port number to set or %GST_URI_NO_PORT to unset. |
| * |
| * Set or unset the port number for the URI. |
| * |
| * Returns: %TRUE if the port number was set/unset successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_port (GstUri * uri, guint port) |
| { |
| if (!uri) |
| return port == GST_URI_NO_PORT; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| uri->port = port; |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_path: |
| * @uri: The #GstUri to get the path from. |
| * |
| * Extract the path string from the URI object. |
| * |
| * Returns: (transfer full): The path from the URI. Once finished with the |
| * string should be g_free()'d. |
| * |
| * Since: 1.6 |
| */ |
| gchar * |
| gst_uri_get_path (const GstUri * uri) |
| { |
| GList *path_segment; |
| const gchar *sep = ""; |
| GString *ret; |
| |
| if (!uri) |
| return NULL; |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| if (!uri->path) |
| return NULL; |
| |
| ret = g_string_new (NULL); |
| |
| for (path_segment = uri->path; path_segment; |
| path_segment = path_segment->next) { |
| g_string_append (ret, sep); |
| if (path_segment->data) { |
| g_string_append (ret, path_segment->data); |
| } |
| sep = "/"; |
| } |
| |
| return g_string_free (ret, FALSE); |
| } |
| |
| /** |
| * gst_uri_set_path: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @path: The new path to set with path segments separated by '/', or use %NULL |
| * to unset the path. |
| * |
| * Sets or unsets the path in the URI. |
| * |
| * Returns: %TRUE if the path was set successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_path (GstUri * uri, const gchar * path) |
| { |
| if (!uri) |
| return path == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| g_list_free_full (uri->path, g_free); |
| uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_path_string: |
| * @uri: The #GstUri to get the path from. |
| * |
| * Extract the path string from the URI object as a percent encoded URI path. |
| * |
| * Returns: (transfer full): The path from the URI. Once finished with the |
| * string should be g_free()'d. |
| * |
| * Since: 1.6 |
| */ |
| gchar * |
| gst_uri_get_path_string (const GstUri * uri) |
| { |
| GList *path_segment; |
| const gchar *sep = ""; |
| GString *ret; |
| gchar *escaped; |
| |
| if (!uri) |
| return NULL; |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| if (!uri->path) |
| return NULL; |
| |
| ret = g_string_new (NULL); |
| |
| for (path_segment = uri->path; path_segment; |
| path_segment = path_segment->next) { |
| g_string_append (ret, sep); |
| if (path_segment->data) { |
| escaped = _gst_uri_escape_path_segment (path_segment->data); |
| g_string_append (ret, escaped); |
| g_free (escaped); |
| } |
| sep = "/"; |
| } |
| |
| return g_string_free (ret, FALSE); |
| } |
| |
| /** |
| * gst_uri_set_path_string: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @path: The new percent encoded path to set with path segments separated by |
| * '/', or use %NULL to unset the path. |
| * |
| * Sets or unsets the path in the URI. |
| * |
| * Returns: %TRUE if the path was set successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_path_string (GstUri * uri, const gchar * path) |
| { |
| if (!uri) |
| return path == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| g_list_free_full (uri->path, g_free); |
| uri->path = _gst_uri_string_to_list (path, "/", FALSE, TRUE); |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_path_segments: |
| * @uri: (nullable): The #GstUri to get the path from. |
| * |
| * Get a list of path segments from the URI. |
| * |
| * Returns: (transfer full)(element-type gchar*): A #GList of path segment |
| * strings or %NULL if no path segments are available. Free the list |
| * when no longer needed with g_list_free_full(list, g_free). |
| * |
| * Since: 1.6 |
| */ |
| GList * |
| gst_uri_get_path_segments (const GstUri * uri) |
| { |
| GList *ret = NULL; |
| |
| g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); |
| |
| if (uri) { |
| ret = g_list_copy_deep (uri->path, (GCopyFunc) g_strdup, NULL); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * gst_uri_set_path_segments: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @path_segments: (transfer full)(nullable)(element-type gchar*): The new |
| * path list to set. |
| * |
| * Replace the path segments list in the URI. |
| * |
| * Returns: %TRUE if the path segments were set successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_path_segments (GstUri * uri, GList * path_segments) |
| { |
| g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), FALSE); |
| |
| if (!uri) { |
| if (path_segments) |
| g_list_free_full (path_segments, g_free); |
| return path_segments == NULL; |
| } |
| |
| g_return_val_if_fail (gst_uri_is_writable (uri), FALSE); |
| |
| g_list_free_full (uri->path, g_free); |
| uri->path = path_segments; |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_append_path: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @relative_path: Relative path to append to the end of the current path. |
| * |
| * Append a path onto the end of the path in the URI. The path is not |
| * normalized, call #gst_uri_normalize() to normalize the path. |
| * |
| * Returns: %TRUE if the path was appended successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_append_path (GstUri * uri, const gchar * relative_path) |
| { |
| GList *rel_path_list; |
| |
| if (!uri) |
| return relative_path == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| if (!relative_path) |
| return TRUE; |
| |
| if (uri->path) { |
| GList *last_elem = g_list_last (uri->path); |
| if (last_elem->data == NULL) { |
| uri->path = g_list_delete_link (uri->path, last_elem); |
| } |
| } |
| rel_path_list = _gst_uri_string_to_list (relative_path, "/", FALSE, FALSE); |
| /* if path was absolute, make it relative by removing initial NULL element */ |
| if (rel_path_list && rel_path_list->data == NULL) { |
| rel_path_list = g_list_delete_link (rel_path_list, rel_path_list); |
| } |
| uri->path = g_list_concat (uri->path, rel_path_list); |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_append_path_segment: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @path_segment: The path segment string to append to the URI path. |
| * |
| * Append a single path segment onto the end of the URI path. |
| * |
| * Returns: %TRUE if the path was appended successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_append_path_segment (GstUri * uri, const gchar * path_segment) |
| { |
| if (!uri) |
| return path_segment == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| if (!path_segment) |
| return TRUE; |
| |
| /* if base path ends in a directory (i.e. last element is NULL), remove it */ |
| if (uri->path && g_list_last (uri->path)->data == NULL) { |
| uri->path = g_list_delete_link (uri->path, g_list_last (uri->path)); |
| } |
| uri->path = g_list_append (uri->path, g_strdup (path_segment)); |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_query_string: |
| * @uri: (nullable): The #GstUri to get the query string from. |
| * |
| * Get a percent encoded URI query string from the @uri. |
| * |
| * Returns: (transfer full): A percent encoded query string. Use g_free() when |
| * no longer needed. |
| * |
| * Since: 1.6 |
| */ |
| gchar * |
| gst_uri_get_query_string (const GstUri * uri) |
| { |
| GHashTableIter iter; |
| gpointer key, value; |
| const gchar *sep = ""; |
| gchar *escaped; |
| GString *ret; |
| |
| if (!uri) |
| return NULL; |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| if (!uri->query) |
| return NULL; |
| |
| ret = g_string_new (NULL); |
| g_hash_table_iter_init (&iter, uri->query); |
| while (g_hash_table_iter_next (&iter, &key, &value)) { |
| g_string_append (ret, sep); |
| escaped = _gst_uri_escape_http_query_element (key); |
| g_string_append (ret, escaped); |
| g_free (escaped); |
| if (value) { |
| escaped = _gst_uri_escape_http_query_element (value); |
| g_string_append_printf (ret, "=%s", escaped); |
| g_free (escaped); |
| } |
| sep = "&"; |
| } |
| |
| return g_string_free (ret, FALSE); |
| } |
| |
| /** |
| * gst_uri_set_query_string: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @query: The new percent encoded query string to use to populate the query |
| * table, or use %NULL to unset the query table. |
| * |
| * Sets or unsets the query table in the URI. |
| * |
| * Returns: %TRUE if the query table was set successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_query_string (GstUri * uri, const gchar * query) |
| { |
| if (!uri) |
| return query == NULL; |
| |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| if (uri->query) |
| g_hash_table_unref (uri->query); |
| uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, TRUE); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_get_query_table: |
| * @uri: (nullable): The #GstUri to get the query table from. |
| * |
| * Get the query table from the URI. Keys and values in the table are freed |
| * with g_free when they are deleted. A value may be %NULL to indicate that |
| * the key should appear in the query string in the URI, but does not have a |
| * value. Free the returned #GHashTable with #g_hash_table_unref() when it is |
| * no longer required. Modifying this hash table will modify the query in the |
| * URI. |
| * |
| * Returns: (transfer full)(element-type gchar* gchar*): The query hash table |
| * from the URI. |
| * |
| * Since: 1.6 |
| */ |
| GHashTable * |
| gst_uri_get_query_table (const GstUri * uri) |
| { |
| if (!uri) |
| return NULL; |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| if (!uri->query) |
| return NULL; |
| |
| return g_hash_table_ref (uri->query); |
| } |
| |
| /** |
| * gst_uri_set_query_table: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @query_table: (transfer none)(nullable)(element-type gchar* gchar*): The new |
| * query table to use. |
| * |
| * Set the query table to use in the URI. The old table is unreferenced and a |
| * reference to the new one is used instead. A value if %NULL for @query_table |
| * will remove the query string from the URI. |
| * |
| * Returns: %TRUE if the new table was sucessfully used for the query table. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_query_table (GstUri * uri, GHashTable * query_table) |
| { |
| GHashTable *old_table = NULL; |
| |
| if (!uri) |
| return query_table == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| old_table = uri->query; |
| if (query_table) |
| uri->query = g_hash_table_ref (query_table); |
| else |
| uri->query = NULL; |
| if (old_table) |
| g_hash_table_unref (old_table); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_set_query_value: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @query_key: (transfer none): The key for the query entry. |
| * @query_value: (transfer none)(nullable): The value for the key. |
| * |
| * This inserts or replaces a key in the query table. A @query_value of %NULL |
| * indicates that the key has no associated value, but will still be present in |
| * the query string. |
| * |
| * Returns: %TRUE if the query table was sucessfully updated. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_query_value (GstUri * uri, const gchar * query_key, |
| const gchar * query_value) |
| { |
| if (!uri) |
| return FALSE; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| if (!uri->query) { |
| uri->query = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, |
| g_free); |
| } |
| g_hash_table_insert (uri->query, g_strdup (query_key), |
| g_strdup (query_value)); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_uri_remove_query_key: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @query_key: The key to remove. |
| * |
| * Remove an entry from the query table by key. |
| * |
| * Returns: %TRUE if the key existed in the table and was removed. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_remove_query_key (GstUri * uri, const gchar * query_key) |
| { |
| gboolean result; |
| |
| if (!uri) |
| return FALSE; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| if (!uri->query) |
| return FALSE; |
| |
| result = g_hash_table_remove (uri->query, query_key); |
| /* if this was the last query entry, remove the query string completely */ |
| if (result && g_hash_table_size (uri->query) == 0) { |
| g_hash_table_unref (uri->query); |
| uri->query = NULL; |
| } |
| return result; |
| } |
| |
| /** |
| * gst_uri_query_has_key: |
| * @uri: (nullable): The #GstUri to examine. |
| * @query_key: The key to lookup. |
| * |
| * Check if there is a query table entry for the @query_key key. |
| * |
| * Returns: %TRUE if @query_key exists in the URI query table. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_query_has_key (const GstUri * uri, const gchar * query_key) |
| { |
| if (!uri) |
| return FALSE; |
| g_return_val_if_fail (GST_IS_URI (uri), FALSE); |
| if (!uri->query) |
| return FALSE; |
| |
| return g_hash_table_contains (uri->query, query_key); |
| } |
| |
| /** |
| * gst_uri_get_query_value: |
| * @uri: (nullable): The #GstUri to examine. |
| * @query_key: The key to lookup. |
| * |
| * Get the value associated with the @query_key key. Will return %NULL if the |
| * key has no value or if the key does not exist in the URI query table. Because |
| * %NULL is returned for both missing keys and keys with no value, you should |
| * use gst_uri_query_has_key() to determine if a key is present in the URI |
| * query. |
| * |
| * Returns: The value for the given key, or %NULL if not found. |
| * |
| * Since: 1.6 |
| */ |
| const gchar * |
| gst_uri_get_query_value (const GstUri * uri, const gchar * query_key) |
| { |
| if (!uri) |
| return NULL; |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| if (!uri->query) |
| return NULL; |
| |
| return g_hash_table_lookup (uri->query, query_key); |
| } |
| |
| /** |
| * gst_uri_get_query_keys: |
| * @uri: (nullable): The #GstUri to examine. |
| * |
| * Get a list of the query keys from the URI. |
| * |
| * Returns: (transfer container)(element-type gchar*): A list of keys from |
| * the URI query. Free the list with g_list_free(). |
| * |
| * Since: 1.6 |
| */ |
| GList * |
| gst_uri_get_query_keys (const GstUri * uri) |
| { |
| if (!uri) |
| return NULL; |
| g_return_val_if_fail (GST_IS_URI (uri), NULL); |
| if (!uri->query) |
| return NULL; |
| |
| return g_hash_table_get_keys (uri->query); |
| } |
| |
| /** |
| * gst_uri_get_fragment: |
| * @uri: (nullable): This #GstUri object. |
| * |
| * Get the fragment name from the URI or %NULL if it doesn't exist. |
| * If @uri is %NULL then returns %NULL. |
| * |
| * Returns: The host name from the #GstUri object or %NULL. |
| * |
| * Since: 1.6 |
| */ |
| const gchar * |
| gst_uri_get_fragment (const GstUri * uri) |
| { |
| g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); |
| return (uri ? uri->fragment : NULL); |
| } |
| |
| /** |
| * gst_uri_set_fragment: |
| * @uri: (transfer none)(nullable): The #GstUri to modify. |
| * @fragment: (nullable): The fragment string to set. |
| * |
| * Sets the fragment string in the URI. Use a value of %NULL in @fragment to |
| * unset the fragment string. |
| * |
| * Returns: %TRUE if the fragment was set/unset successfully. |
| * |
| * Since: 1.6 |
| */ |
| gboolean |
| gst_uri_set_fragment (GstUri * uri, const gchar * fragment) |
| { |
| if (!uri) |
| return fragment == NULL; |
| g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); |
| |
| g_free (uri->fragment); |
| uri->fragment = g_strdup (fragment); |
| return TRUE; |
| } |