gst-inspect: fix unused-const-variable error in windows
[platform/upstream/gstreamer.git] / gst / gsturi.c
index 66ab40c..a5b7190 100644 (file)
@@ -2,8 +2,10 @@
  * 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
  *
  * 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., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 
 /**
  * SECTION:gsturihandler
+ * @title: 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.
+ * The #GstURIHandler is an interface that is implemented by Source and Sink
+ * #GstElement to unify 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.
- *
- * Last reviewed on 2005-11-09 (0.9.4)
  */
 
 #ifdef HAVE_CONFIG_H
 #endif
 
 #include "gst_private.h"
+#include "gst.h"
 #include "gsturi.h"
 #include "gstinfo.h"
-#include "gstmarshal.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
 
-enum
-{
-  NEW_URI,
-  LAST_SIGNAL
-};
+#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.
+ */
 
-static guint gst_uri_handler_signals[LAST_SIGNAL] = { 0 };
+/*
+ * Find the first occurrence of find in s, ignore case.
+ */
 
-static void gst_uri_handler_base_init (gpointer g_class);
+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
 
 GType
 gst_uri_handler_get_type (void)
@@ -71,7 +128,7 @@ gst_uri_handler_get_type (void)
     GType _type;
     static const GTypeInfo urihandler_info = {
       sizeof (GstURIHandlerInterface),
-      gst_uri_handler_base_init,
+      NULL,
       NULL,
       NULL,
       NULL,
@@ -92,29 +149,15 @@ gst_uri_handler_get_type (void)
   return urihandler_type;
 }
 
-static void
-gst_uri_handler_base_init (gpointer g_class)
+GQuark
+gst_uri_error_quark (void)
 {
-  static gboolean initialized = FALSE;
-
-  if (G_UNLIKELY (!initialized)) {
-
-    /**
-     * GstURIHandler::new-uri:
-     * @handler: The #GstURIHandler which emitted the signal
-     * @uri: (transfer none): The new URI, or NULL if the URI was removed
-     *
-     * The URI of the given @handler has changed.
-     */
-
-    gst_uri_handler_signals[NEW_URI] =
-        g_signal_new ("new-uri", GST_TYPE_URI_HANDLER, G_SIGNAL_RUN_LAST,
-        G_STRUCT_OFFSET (GstURIHandlerInterface, new_uri), NULL, NULL,
-        gst_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
-    initialized = TRUE;
-  }
+  return g_quark_from_static_string ("gst-uri-error-quark");
 }
 
+#define HEX_ESCAPE '%'
+
+#ifndef GST_REMOVE_DEPRECATED
 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:;<=>?   */
@@ -134,8 +177,6 @@ typedef enum
   UNSAFE_SLASHES = 0x20         /* Allows all characters except for '/' and '%' */
 } UnsafeCharacterSet;
 
-#define HEX_ESCAPE '%'
-
 /*  Escape undesirable characters using %
  *  -------------------------------------
  *
@@ -208,21 +249,7 @@ escape_string_internal (const gchar * string, UnsafeCharacterSet mask)
 
   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);
-}
+#endif
 
 static int
 hex_to_int (gchar c)
@@ -260,9 +287,9 @@ unescape_character (const char *scanner)
  * Characters are encoded in PERCENTxy form, where xy is the ASCII hex code
  * for character 16x+y.
  *
- * Return value: a newly allocated string with the unescaped equivalents,
- * or %NULL if @escaped_string contained one of the characters
- * in @illegal_characters.
+ * 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)
@@ -328,7 +355,7 @@ gst_uri_protocol_check_internal (const gchar * uri, gchar ** endptr)
  * 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.
+ * Returns: %TRUE if the string is a valid protocol identifier, %FALSE otherwise.
  */
 gboolean
 gst_uri_protocol_is_valid (const gchar * protocol)
@@ -339,7 +366,7 @@ gst_uri_protocol_is_valid (const gchar * protocol)
 
   gst_uri_protocol_check_internal (protocol, &endptr);
 
-  return *endptr == '\0' && endptr != protocol;
+  return *endptr == '\0' && ((gsize) (endptr - protocol)) >= 2;
 }
 
 /**
@@ -349,7 +376,7 @@ gst_uri_protocol_is_valid (const gchar * protocol)
  * 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
+ * Returns: %TRUE if the string is a valid URI
  */
 gboolean
 gst_uri_is_valid (const gchar * uri)
@@ -360,7 +387,7 @@ gst_uri_is_valid (const gchar * uri)
 
   gst_uri_protocol_check_internal (uri, &endptr);
 
-  return *endptr == ':';
+  return *endptr == ':' && ((gsize) (endptr - uri)) >= 2;
 }
 
 /**
@@ -370,7 +397,7 @@ gst_uri_is_valid (const gchar * uri)
  * Extracts the protocol out of a given valid URI. The returned string must be
  * freed using g_free().
  *
- * Returns: The protocol for this URI.
+ * Returns: (nullable): The protocol for this URI.
  */
 gchar *
 gst_uri_get_protocol (const gchar * uri)
@@ -393,8 +420,6 @@ gst_uri_get_protocol (const gchar * uri)
  * Checks if the protocol of a given valid URI matches @protocol.
  *
  * Returns: %TRUE if the protocol matches.
- *
- * Since: 0.10.4
  */
 gboolean
 gst_uri_has_protocol (const gchar * uri, const gchar * protocol)
@@ -424,9 +449,9 @@ gst_uri_has_protocol (const gchar * uri, const gchar * protocol)
  *
  * Free-function: g_free
  *
- * Returns: (transfer full) (array zero-terminated=1): 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.
+ * Returns: (transfer full) (nullable): 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)
@@ -453,7 +478,7 @@ gst_uri_get_location (const gchar * uri)
       g_ascii_isalpha (unescaped[1]) &&
       (unescaped[2] == ':' || unescaped[2] == '|')) {
     unescaped[2] = ':';
-    g_memmove (unescaped, unescaped + 1, strlen (unescaped + 1) + 1);
+    memmove (unescaped, unescaped + 1, strlen (unescaped + 1) + 1);
   }
 #endif
 
@@ -465,16 +490,18 @@ gst_uri_get_location (const gchar * uri)
 /**
  * gst_uri_construct:
  * @protocol: Protocol for URI
- * @location: (array zero-terminated=1) (transfer none): Location 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) (array zero-terminated=1): a new string for this
- *     URI. Returns NULL if the given URI protocol is not valid, or the given
- *     location is NULL.
+ * 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.
+ *
+ * Deprecated: Use GstURI instead.
  */
+#ifndef GST_REMOVE_DEPRECATED
 gchar *
 gst_uri_construct (const gchar * protocol, const gchar * location)
 {
@@ -485,13 +512,14 @@ gst_uri_construct (const gchar * protocol, const gchar * location)
   g_return_val_if_fail (location != NULL, NULL);
 
   proto_lowercase = g_ascii_strdown (protocol, -1);
-  escaped = escape_string (location);
+  escaped = escape_string_internal (location, UNSAFE_PATH);
   retval = g_strdup_printf ("%s://%s", proto_lowercase, escaped);
   g_free (escaped);
   g_free (proto_lowercase);
 
   return retval;
 }
+#endif
 
 typedef struct
 {
@@ -503,7 +531,7 @@ SearchEntry;
 static gboolean
 search_by_entry (GstPluginFeature * feature, gpointer search_entry)
 {
-  gchar **protocols;
+  const gchar *const *protocols;
   GstElementFactory *factory;
   SearchEntry *entry = (SearchEntry *) search_entry;
 
@@ -548,7 +576,7 @@ get_element_factories_from_uri_protocol (const GstURIType type,
 
   entry.type = type;
   entry.protocol = protocol;
-  possibilities = gst_registry_feature_filter (gst_registry_get_default (),
+  possibilities = gst_registry_feature_filter (gst_registry_get (),
       search_by_entry, FALSE, &entry);
 
   return possibilities;
@@ -563,9 +591,7 @@ get_element_factories_from_uri_protocol (const GstURIType type,
  * that a positive return value does not imply that a subsequent call to
  * gst_element_make_from_uri() is guaranteed to work.
  *
- * Returns: TRUE
- *
- * Since: 0.10.13
+ * Returns: %TRUE
 */
 gboolean
 gst_uri_protocol_is_supported (const GstURIType type, const gchar * protocol)
@@ -587,43 +613,65 @@ gst_uri_protocol_is_supported (const GstURIType type, const gchar * protocol)
  * 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.
+ * @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 full): a new element or NULL if none could be created
+ * Returns: (transfer floating) (nullable): 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)
+    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);
-  g_free (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) {
-    if ((ret =
-            gst_element_factory_create (GST_ELEMENT_FACTORY_CAST (walk->data),
-                elementname)) != NULL) {
+    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))
+      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;
     }
@@ -633,6 +681,11 @@ gst_element_make_from_uri (const GstURIType type, const gchar * uri,
 
   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;
 }
 
@@ -645,11 +698,11 @@ gst_element_make_from_uri (const GstURIType type, const gchar * uri,
  * Returns: the #GstURIType of the URI handler.
  * Returns #GST_URI_UNKNOWN if the @handler isn't implemented correctly.
  */
-guint
+GstURIType
 gst_uri_handler_get_uri_type (GstURIHandler * handler)
 {
   GstURIHandlerInterface *iface;
-  guint ret;
+  GstURIType ret;
 
   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), GST_URI_UNKNOWN);
 
@@ -670,15 +723,16 @@ gst_uri_handler_get_uri_type (GstURIHandler * handler)
  * Gets the list of protocols supported by @handler. This list may not be
  * modified.
  *
- * Returns: (transfer none) (array zero-terminated=1) (element-type utf8): the
- *     supported protocols. Returns NULL if the @handler isn't implemented
- *     properly, or the @handler doesn't support any protocols.
+ * 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.
  */
-gchar **
+const gchar *const *
 gst_uri_handler_get_protocols (GstURIHandler * handler)
 {
   GstURIHandlerInterface *iface;
-  gchar **ret;
+  const gchar *const *ret;
 
   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
 
@@ -698,15 +752,16 @@ gst_uri_handler_get_protocols (GstURIHandler * handler)
  *
  * Gets the currently handled URI.
  *
- * Returns: (transfer none): the URI currently handled by the @handler.
- *   Returns NULL if there are no URI currently handled. The
- *   returned string must not be modified or freed.
+ * 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.
  */
-const gchar *
+gchar *
 gst_uri_handler_get_uri (GstURIHandler * handler)
 {
   GstURIHandlerInterface *iface;
-  const gchar *ret;
+  gchar *ret;
 
   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
 
@@ -724,20 +779,24 @@ gst_uri_handler_get_uri (GstURIHandler * handler)
  * 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.
+ * Returns: %TRUE if the URI was set successfully, else %FALSE.
  */
 gboolean
-gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri)
+gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri,
+    GError ** error)
 {
   GstURIHandlerInterface *iface;
   gboolean ret;
-  gchar *new_uri, *protocol, *location, *colon;
+  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);
@@ -745,36 +804,36 @@ gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri)
 
   protocol = gst_uri_get_protocol (uri);
 
-  colon = strstr (uri, ":");
-  location = g_strdup (colon);
+  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;
+        }
+      }
 
-  new_uri = g_strdup_printf ("%s%s", protocol, location);
+      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);
+  ret = iface->set_uri (handler, uri, error);
 
-  g_free (new_uri);
-  g_free (location);
   g_free (protocol);
 
   return ret;
 }
 
-/**
- * gst_uri_handler_new_uri:
- * @handler: A #GstURIHandler
- * @uri: new URI or NULL if it was unset
- *
- * Emits the new-uri signal for a given handler, when that handler has a new URI.
- * This function should only be called by URI handlers themselves.
- */
-void
-gst_uri_handler_new_uri (GstURIHandler * handler, const gchar * uri)
-{
-  g_return_if_fail (GST_IS_URI_HANDLER (handler));
-
-  g_signal_emit (handler, gst_uri_handler_signals[NEW_URI], 0, uri);
-}
-
 static gchar *
 gst_file_utils_canonicalise_path (const gchar * path)
 {
@@ -794,7 +853,7 @@ gst_file_utils_canonicalise_path (const gchar * path)
     if (strcmp (*p, ".") == 0) {
       /* just move all following parts on top of this, incl. NUL terminator */
       g_free (*p);
-      g_memmove (p, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *));
+      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) {
@@ -802,7 +861,7 @@ gst_file_utils_canonicalise_path (const gchar * path)
        * NUL terminator */
       g_free (*(p - 1));
       g_free (*p);
-      g_memmove (p - 1, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *));
+      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;
@@ -814,7 +873,7 @@ gst_file_utils_canonicalise_path (const gchar * path)
 
     num_parts = g_strv_length (parts) + 1;      /* incl. terminator */
     parts = g_renew (gchar *, parts, num_parts + 1);
-    g_memmove (parts + 1, parts, num_parts * sizeof (gchar *));
+    memmove (parts + 1, parts, num_parts * sizeof (gchar *));
     parts[0] = g_strdup ("/");
   }
 
@@ -833,8 +892,8 @@ file_path_contains_relatives (const gchar * path)
 
 /**
  * gst_filename_to_uri:
- * @filename: absolute or relative file name path
- * @error: pointer to error, or NULL
+ * @filename: (type 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
@@ -843,7 +902,8 @@ file_path_contains_relatives (const gchar * path)
  *
  * On Windows #filename should be in UTF-8 encoding.
  *
- * Since: 0.10.33
+ * 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)
@@ -886,3 +946,1889 @@ beach:
   GST_DEBUG ("'%s' -> '%s'", filename, uri);
   return uri;
 }
+
+/****************************************************************************
+ * GstUri - GstMiniObject to parse and merge URIs according to IETF RFC 3986
+ ****************************************************************************/
+
+/**
+ * SECTION:gsturi
+ * @title: GstUri
+ * @short_description: URI parsing and manipulation.
+ *
+ * A #GstUri object can be used to parse and split a URI string into its
+ * constituent 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;
+
+  g_return_val_if_fail (gst_is_initialized (), NULL);
+
+  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);
+
+#ifdef USE_POISONING
+  memset (uri, 0xff, sizeof (*uri));
+#endif
+
+  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);
+      if (next != NULL) {
+        out = g_list_delete_link (out, elem);
+      } else {
+        /* path ends in '/..' We need to keep the last '/' */
+        elem->data = NULL;
+      }
+    }
+  }
+
+  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 "&percnt;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 "&percnt;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_unref (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_unref (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) (nullable): 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: (nullable): 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: (nullable): 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: (nullable): 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) (nullable): 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) (nullable): 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) (nullable): 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*) (nullable): 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 successfully 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 successfully 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: (nullable): 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: (nullable): 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_uri_get_media_fragment_table:
+ * @uri: (nullable): The #GstUri to get the fragment table from.
+ *
+ * Get the media fragment table from the URI, as defined by "Media Fragments URI 1.0".
+ * Hash table returned by this API is a list of "key-value" pairs, and the each
+ * pair is generated by splitting "URI fragment" per "&" sub-delims, then "key"
+ * and "value" are split by "=" sub-delims. The "key" returned by this API may
+ * be undefined keyword by standard.
+ * A value may be %NULL to indicate that the key should appear in the fragment
+ * 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 does not affect the fragment in the URI.
+ *
+ * See more about Media Fragments URI 1.0 (W3C) at https://www.w3.org/TR/media-frags/
+ *
+ * Returns: (transfer full) (element-type gchar* gchar*) (nullable): The
+ *          fragment hash table from the URI.
+ *
+ * Since: 1.12
+ */
+GHashTable *
+gst_uri_get_media_fragment_table (const GstUri * uri)
+{
+  g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
+
+  if (!uri->fragment)
+    return NULL;
+  return _gst_uri_string_to_table (uri->fragment, "&", "=", TRUE, TRUE);
+}