gstreamer/gst/gsturi.c

2816 lines
76 KiB
C

/* 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;
}