* 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
+ * 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
#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
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_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;
+ 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, *pct_kv_sep;
+ 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.
+ *
+ * Returns: (transfer full): A new #GstUri object.
+ *
+ * Since: 1.6
+ */
+GstUri *
+gst_uri_from_string (const gchar * uri)
+{
+ GstUri *uri_obj;
+
+ uri_obj = _gst_uri_new ();
+
+ if (uri_obj && uri != NULL) {
+ int i = 0;
+ 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;
+ /* get authority [userinfo@]host[:port] */
+ uri += 2;
+ /* find end of authority */
+ eoa = strchr (uri, '/');
+ if (eoa == NULL)
+ eoa = uri + strlen (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)
+ eoh = eoa - 1;
+ } else {
+ eoh = strchr (uri, ':');
+ if (eoh == NULL || eoh >= eoa)
+ eoh = eoa - 1;
+ else
+ eoh--;
+ }
+ uri_obj->host = g_uri_unescape_segment (uri, eoh + 1, NULL);
+ uri = eoh + 1;
+ if (uri < eoa) {
+ /* if port number is malformed, do best effort and concat string */
+ if (uri[0] != ':' || strspn (uri + 1, "0123456789") != eoa - uri - 1) {
+ gchar *tmp = uri_obj->host;
+ uri_obj->host = g_malloc (strlen (uri_obj->host) + eoa - uri + 1);
+ g_strlcpy (g_stpcpy (uri_obj->host, tmp), uri, eoa - uri + 1);
+ g_free (tmp);
+ } else {
+ /* 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 (mrgd);
+ }
+ 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) {
+ 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;
+}
GST_END_TEST;
+GST_START_TEST (test_url_parsing)
+{
+ GstUri *url;
+ GList *list;
+ gchar *tmp_str;
+
+ url =
+ gst_uri_from_string
+ ("scheme://user:pass@host.com:1234/path/to/item-obj?query=something#fragment");
+
+ fail_unless_equals_string (gst_uri_get_scheme (url), "scheme");
+ fail_unless_equals_string (gst_uri_get_userinfo (url), "user:pass");
+ fail_unless_equals_string (gst_uri_get_host (url), "host.com");
+ fail_unless (gst_uri_get_port (url) == 1234);
+ tmp_str = gst_uri_get_path (url);
+ fail_unless_equals_string (tmp_str, "/path/to/item-obj");
+ g_free (tmp_str);
+ list = gst_uri_get_query_keys (url);
+ fail_unless (g_list_length (list) == 1);
+ g_list_free (list);
+ fail_unless (gst_uri_query_has_key (url, "query"));
+ fail_unless_equals_string (gst_uri_get_query_value (url, "query"),
+ "something");
+ fail_unless_equals_string (gst_uri_get_fragment (url), "fragment");
+ gst_uri_unref (url);
+
+ url = gst_uri_from_string ("scheme://host/path/to/dir/");
+ fail_unless_equals_string (gst_uri_get_scheme (url), "scheme");
+ fail_unless (gst_uri_get_userinfo (url) == NULL);
+ fail_unless_equals_string (gst_uri_get_host (url), "host");
+ fail_unless (gst_uri_get_port (url) == GST_URI_NO_PORT);
+ tmp_str = gst_uri_get_path (url);
+ fail_unless_equals_string (tmp_str, "/path/to/dir/");
+ g_free (tmp_str);
+ fail_unless (gst_uri_get_query_table (url) == NULL);
+ fail_unless (gst_uri_get_fragment (url) == NULL);
+ gst_uri_unref (url);
+
+ url = gst_uri_from_string ("urn:name:path");
+ fail_unless_equals_string (gst_uri_get_scheme (url), "urn");
+ fail_unless (gst_uri_get_userinfo (url) == NULL);
+ fail_unless (gst_uri_get_host (url) == NULL);
+ fail_unless (gst_uri_get_port (url) == GST_URI_NO_PORT);
+ tmp_str = gst_uri_get_path (url);
+ fail_unless_equals_string (tmp_str, "name:path");
+ g_free (tmp_str);
+ list = gst_uri_get_query_keys (url);
+ fail_unless (g_list_length (list) == 0);
+ g_list_free (list);
+ fail_unless (gst_uri_get_fragment (url) == NULL);
+ gst_uri_unref (url);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_url_normalization)
+{
+ GstUri *url;
+ gchar *tmp_str;
+
+ url =
+ gst_uri_from_string
+ ("ScHeMe://User:P%61ss@HOST.%63om:1234/path/./from/../to%7d/item%2dobj?qu%65ry=something#fr%61gment");
+ fail_unless (gst_uri_normalize (url));
+ fail_unless_equals_string (gst_uri_get_scheme (url), "scheme");
+ fail_unless_equals_string (gst_uri_get_userinfo (url), "User:Pass");
+ fail_unless_equals_string (gst_uri_get_host (url), "host.com");
+ tmp_str = gst_uri_get_path (url);
+ fail_unless_equals_string (tmp_str, "/path/to}/item-obj");
+ g_free (tmp_str);
+ fail_unless (gst_uri_query_has_key (url, "query"));
+ fail_unless_equals_string (gst_uri_get_query_value (url, "query"),
+ "something");
+ fail_unless_equals_string (gst_uri_get_fragment (url), "fragment");
+ gst_uri_unref (url);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_url_joining)
+{
+ GstUri *base, *rel, *joined;
+ gchar *l;
+
+ base =
+ gst_uri_from_string
+ ("http://example.com/path/to/dir/filename.html#fragment");
+
+ /* test change of fragment only */
+ rel = gst_uri_from_string ("#new_frag");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/dir/filename.html#new_frag");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test addition of new query string */
+ rel = gst_uri_from_string ("?key=val");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/dir/filename.html?key=val");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test new base filename */
+ rel = gst_uri_from_string ("new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/dir/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test relative file same directory */
+ rel = gst_uri_from_string ("./new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/dir/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test relative file parent directory */
+ rel = gst_uri_from_string ("../new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l, "http://example.com/path/to/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test relative file grandparent directory */
+ rel = gst_uri_from_string ("../../new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l, "http://example.com/path/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test relative file root directory */
+ rel = gst_uri_from_string ("../../../new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l, "http://example.com/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test relative file beyond root directory */
+ rel = gst_uri_from_string ("../../../../new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l, "http://example.com/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test add subdirectory */
+ rel = gst_uri_from_string ("subdir/new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/dir/subdir/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test change directory */
+ rel = gst_uri_from_string ("../subdir/new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/subdir/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ gst_uri_unref (base);
+
+ /* change base for path ending in directory */
+ base = gst_uri_from_string ("http://example.com/path/to/dir/");
+
+ /* test adding file to directory */
+ rel = gst_uri_from_string ("new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/dir/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test adding file to directory using relative path */
+ rel = gst_uri_from_string ("./new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l,
+ "http://example.com/path/to/dir/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test filename in parent directory */
+ rel = gst_uri_from_string ("../new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l, "http://example.com/path/to/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ /* test replace with absolute */
+ rel = gst_uri_from_string ("https://ssl.example.com/new_filename.xml");
+ joined = gst_uri_join (base, rel);
+ l = gst_uri_to_string (joined);
+ fail_unless_equals_string (l, "https://ssl.example.com/new_filename.xml");
+ g_free (l);
+ gst_uri_unref (joined);
+ gst_uri_unref (rel);
+
+ gst_uri_unref (base);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_url_equality)
+{
+ GstUri *url1, *url2;
+
+ url1 =
+ gst_uri_from_string
+ ("ScHeMe://User:Pass@HOST.com:1234/path/./from/../to%7d/item%2dobj?query=something#fragment");
+
+ /* equal */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (gst_uri_equal (url1, url2));
+ fail_unless (gst_uri_equal (url2, url1));
+ gst_uri_unref (url2);
+
+ /* different fragment */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something#different-fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different query */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=different-something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different path */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:1234/path/to%7D/different-item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different port */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:4321/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different host */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@different-host.com:1234/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different userinfo */
+ url2 =
+ gst_uri_from_string
+ ("scheme://Different-User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different scheme */
+ url2 =
+ gst_uri_from_string
+ ("different+scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different (no scheme) */
+ url2 =
+ gst_uri_from_string
+ ("//User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different (no userinfo) */
+ url2 =
+ gst_uri_from_string
+ ("scheme://host.com:1234/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different (no host) */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@:1234/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different (no port) */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com/path/to%7D/item-obj?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different (no path) */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:1234?query=something#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different (no query) */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj#fragment");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* different (no fragment) */
+ url2 =
+ gst_uri_from_string
+ ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ /* compare two NULL uris */
+ fail_unless (gst_uri_equal (NULL, NULL));
+
+ /* compare same object */
+ fail_unless (gst_uri_equal (url1, url1));
+
+ /* compare one NULL and one non-NULL uri */
+ fail_unless (!gst_uri_equal (url1, NULL));
+ fail_unless (!gst_uri_equal (NULL, url1));
+
+ gst_uri_unref (url1);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_url_constructors)
+{
+ GstUri *url1, *url2;
+ gchar *tmp_str;
+ GHashTable *tmp_table;
+
+ url1 =
+ gst_uri_new ("scheme", "userinfo", "hostname", 1234, "/path/to/file",
+ "query", "fragment");
+ fail_unless_equals_string (gst_uri_get_scheme (url1), "scheme");
+ fail_unless_equals_string (gst_uri_get_userinfo (url1), "userinfo");
+ fail_unless_equals_string (gst_uri_get_host (url1), "hostname");
+ fail_unless (gst_uri_get_port (url1) == 1234);
+ tmp_str = gst_uri_get_path (url1);
+ fail_unless_equals_string (tmp_str, "/path/to/file");
+ g_free (tmp_str);
+ tmp_table = gst_uri_get_query_table (url1);
+ fail_unless (g_hash_table_size (tmp_table) == 1);
+ fail_unless (g_hash_table_contains (tmp_table, "query"));
+ fail_unless (g_hash_table_lookup (tmp_table, "query") == NULL);
+ g_hash_table_unref (tmp_table);
+ fail_unless_equals_string (gst_uri_get_fragment (url1), "fragment");
+ tmp_str = gst_uri_to_string (url1);
+ fail_unless_equals_string (tmp_str,
+ "scheme://userinfo@hostname:1234/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ url2 =
+ gst_uri_new_with_base (url1, NULL, NULL, NULL, GST_URI_NO_PORT,
+ "new_file", NULL, NULL);
+ fail_unless_equals_string (gst_uri_get_scheme (url2), "scheme");
+ fail_unless_equals_string (gst_uri_get_userinfo (url2), "userinfo");
+ fail_unless_equals_string (gst_uri_get_host (url2), "hostname");
+ fail_unless (gst_uri_get_port (url2) == 1234);
+ tmp_str = gst_uri_get_path (url2);
+ fail_unless_equals_string (tmp_str, "/path/to/new_file");
+ g_free (tmp_str);
+ fail_unless (gst_uri_get_query_table (url2) == NULL);
+ fail_unless (gst_uri_get_fragment (url2) == NULL);
+ tmp_str = gst_uri_to_string (url2);
+ fail_unless_equals_string (tmp_str,
+ "scheme://userinfo@hostname:1234/path/to/new_file");
+ g_free (tmp_str);
+ gst_uri_unref (url2);
+
+ url2 = gst_uri_from_string_with_base (url1, "/a/new/path/to/file");
+ fail_unless_equals_string (gst_uri_get_scheme (url2), "scheme");
+ fail_unless_equals_string (gst_uri_get_userinfo (url2), "userinfo");
+ fail_unless_equals_string (gst_uri_get_host (url2), "hostname");
+ fail_unless (gst_uri_get_port (url2) == 1234);
+ tmp_str = gst_uri_get_path (url2);
+ fail_unless_equals_string (tmp_str, "/a/new/path/to/file");
+ g_free (tmp_str);
+ fail_unless (gst_uri_get_query_table (url2) == NULL);
+ fail_unless (gst_uri_get_fragment (url2) == NULL);
+ tmp_str = gst_uri_to_string (url2);
+ fail_unless_equals_string (tmp_str,
+ "scheme://userinfo@hostname:1234/a/new/path/to/file");
+ g_free (tmp_str);
+ gst_uri_unref (url2);
+
+ url2 = gst_uri_copy (url1);
+ fail_unless (gst_uri_equal (url1, url2));
+ gst_uri_set_query_value (url2, "key", "value");
+ fail_unless (!gst_uri_equal (url1, url2));
+ gst_uri_unref (url2);
+
+ gst_uri_unref (url1);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_url_get_set)
+{
+ GstUri *url;
+ gchar *tmp_str;
+ GList *tmp_list;
+
+ url = gst_uri_from_string ("scheme://hostname/path/to/file?query#fragment");
+
+ fail_unless (gst_uri_set_scheme (url, "new+scheme"));
+ fail_unless_equals_string (gst_uri_get_scheme (url), "new+scheme");
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "new+scheme://hostname/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (gst_uri_set_scheme (url, NULL));
+ fail_unless (gst_uri_get_scheme (url) == NULL);
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str, "//hostname/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_set_scheme (NULL, "fail"));
+ fail_unless (gst_uri_set_scheme (NULL, NULL));
+
+ fail_unless (gst_uri_set_userinfo (url, "username:password"));
+ fail_unless_equals_string (gst_uri_get_userinfo (url), "username:password");
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//username:password@hostname/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (gst_uri_set_userinfo (url, NULL));
+ fail_unless (gst_uri_get_userinfo (url) == NULL);
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str, "//hostname/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_set_userinfo (NULL, "fail"));
+ fail_unless (gst_uri_set_userinfo (NULL, NULL));
+
+ fail_unless (gst_uri_set_host (url, NULL));
+ fail_unless (gst_uri_get_host (url) == NULL);
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str, "/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (gst_uri_set_host (url, "example.com"));
+ fail_unless_equals_string (gst_uri_get_host (url), "example.com");
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_set_host (NULL, "fail"));
+ fail_unless (gst_uri_set_host (NULL, NULL));
+
+ fail_unless (gst_uri_set_port (url, 12345));
+ fail_unless (gst_uri_get_port (url) == 12345);
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com:12345/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (gst_uri_set_port (url, GST_URI_NO_PORT));
+ fail_unless (gst_uri_get_port (url) == GST_URI_NO_PORT);
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_set_port (NULL, 1234));
+ fail_unless (gst_uri_set_port (NULL, GST_URI_NO_PORT));
+
+ fail_unless (gst_uri_append_path_segment (url, "here"));
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/here?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_append_path_segment (NULL, "fail"));
+ fail_unless (gst_uri_append_path_segment (NULL, NULL));
+
+ fail_unless (gst_uri_append_path (url, "../there"));
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/here/../there?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_append_path (NULL, "fail"));
+ fail_unless (gst_uri_append_path (NULL, NULL));
+
+ gst_uri_normalize (url);
+
+ tmp_list = gst_uri_get_path_segments (url);
+ fail_unless (tmp_list != NULL);
+ tmp_list = g_list_append (tmp_list, g_strdup ("segment"));
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/there?query#fragment");
+ g_free (tmp_str);
+ fail_unless (gst_uri_set_path_segments (url, tmp_list));
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/there/segment?query#fragment");
+ g_free (tmp_str);
+
+ tmp_list = g_list_append (NULL, g_strdup ("test"));
+ fail_unless (!gst_uri_set_path_segments (NULL, tmp_list));
+ fail_unless (gst_uri_set_path_segments (NULL, NULL));
+
+ fail_unless (gst_uri_set_query_value (url, "key", "value"));
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/there/segment?query&key=value#fragment");
+ g_free (tmp_str);
+
+ fail_unless (gst_uri_set_query_value (url, "key", NULL));
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/there/segment?query&key#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_set_query_value (NULL, "key", "value"));
+
+ fail_unless (gst_uri_remove_query_key (url, "key"));
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/there/segment?query#fragment");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_remove_query_key (url, "key"));
+ fail_unless (!gst_uri_remove_query_key (NULL, "key"));
+
+ fail_unless (gst_uri_set_fragment (url, NULL));
+ fail_unless (gst_uri_get_fragment (url) == NULL);
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/there/segment?query");
+ g_free (tmp_str);
+
+ fail_unless (gst_uri_set_fragment (url, "tag"));
+ fail_unless_equals_string (gst_uri_get_fragment (url), "tag");
+ tmp_str = gst_uri_to_string (url);
+ fail_unless_equals_string (tmp_str,
+ "//example.com/path/to/file/there/segment?query#tag");
+ g_free (tmp_str);
+
+ fail_unless (!gst_uri_set_fragment (NULL, "can't set if no URI"));
+ fail_unless (gst_uri_set_fragment (NULL, NULL));
+
+ gst_uri_unref (url);
+}
+
+GST_END_TEST;
+
static Suite *
gst_uri_suite (void)
{
#ifdef G_OS_WIN32
tcase_add_test (tc_chain, test_win32_uri);
#endif
+ tcase_add_test (tc_chain, test_url_parsing);
+ tcase_add_test (tc_chain, test_url_normalization);
+ tcase_add_test (tc_chain, test_url_joining);
+ tcase_add_test (tc_chain, test_url_equality);
+ tcase_add_test (tc_chain, test_url_constructors);
+ tcase_add_test (tc_chain, test_url_get_set);
return s;
}