Add separate def for extra size time
[platform/upstream/gstreamer.git] / gst / gsturi.c
1 /* GStreamer
2  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3  *                    2000 Wim Taymans <wtay@chello.be>
4  * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net>
5  * Copyright (C) 2014 David Waring, British Broadcasting Corporation
6  *                        <david.waring@rd.bbc.co.uk>
7  *
8  * gsturi.c: register URI handlers and IETF RFC 3986 URI manipulations.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the
22  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  */
25
26 /**
27  * SECTION:gsturihandler
28  * @short_description: Interface to ease URI handling in plugins.
29  *
30  * The #GstURIHandler is an interface that is implemented by Source and Sink
31  * #GstElement to unify handling of URI.
32  *
33  * An application can use the following functions to quickly get an element
34  * that handles the given URI for reading or writing
35  * (gst_element_make_from_uri()).
36  *
37  * Source and Sink plugins should implement this interface when possible.
38  */
39
40 #ifdef HAVE_CONFIG_H
41 #  include "config.h"
42 #endif
43
44 #include "gst_private.h"
45 #include "gst.h"
46 #include "gsturi.h"
47 #include "gstinfo.h"
48 #include "gstregistry.h"
49
50 #include "gst-i18n-lib.h"
51
52 #include <string.h>
53 #include <glib.h>
54 #include <glib/gprintf.h>
55
56 GST_DEBUG_CATEGORY_STATIC (gst_uri_handler_debug);
57 #define GST_CAT_DEFAULT gst_uri_handler_debug
58
59 #ifndef HAVE_STRCASESTR
60 #define strcasestr _gst_ascii_strcasestr
61
62 /* From https://github.com/freebsd/freebsd/blob/master/contrib/file/src/strcasestr.c
63  * Updated to use GLib types and GLib string functions
64  *
65  * Copyright (c) 1990, 1993
66  *      The Regents of the University of California.  All rights reserved.
67  *
68  * This code is derived from software contributed to Berkeley by
69  * Chris Torek.
70  *
71  * Redistribution and use in source and binary forms, with or without
72  * modification, are permitted provided that the following conditions
73  * are met:
74  * 1. Redistributions of source code must retain the above copyright
75  *    notice, this list of conditions and the following disclaimer.
76  * 2. Redistributions in binary form must reproduce the above copyright
77  *    notice, this list of conditions and the following disclaimer in the
78  *    documentation and/or other materials provided with the distribution.
79  * 3. Neither the name of the University nor the names of its contributors
80  *    may be used to endorse or promote products derived from this software
81  *    without specific prior written permission.
82  *
83  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
84  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
85  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
86  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
87  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
88  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
89  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
90  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
91  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
92  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
93  * SUCH DAMAGE.
94  */
95
96 /*
97  * Find the first occurrence of find in s, ignore case.
98  */
99
100 static gchar *
101 _gst_ascii_strcasestr (const gchar * s, const gchar * find)
102 {
103   gchar c, sc;
104   gsize len;
105
106   if ((c = *find++) != 0) {
107     c = g_ascii_tolower (c);
108     len = strlen (find);
109     do {
110       do {
111         if ((sc = *s++) == 0)
112           return (NULL);
113       } while (g_ascii_tolower (sc) != c);
114     } while (g_ascii_strncasecmp (s, find, len) != 0);
115     s--;
116   }
117   return (gchar *) (gintptr) (s);
118 }
119 #endif
120
121 #if !GLIB_CHECK_VERSION (2, 33, 4)
122 #define g_list_copy_deep gst_g_list_copy_deep
123 static GList *
124 gst_g_list_copy_deep (GList * list, GCopyFunc func, gpointer user_data)
125 {
126   list = g_list_copy (list);
127
128   if (func != NULL) {
129     GList *l;
130
131     for (l = list; l != NULL; l = l->next) {
132       l->data = func (l->data, user_data);
133     }
134   }
135
136   return list;
137 }
138 #endif
139
140 GType
141 gst_uri_handler_get_type (void)
142 {
143   static volatile gsize urihandler_type = 0;
144
145   if (g_once_init_enter (&urihandler_type)) {
146     GType _type;
147     static const GTypeInfo urihandler_info = {
148       sizeof (GstURIHandlerInterface),
149       NULL,
150       NULL,
151       NULL,
152       NULL,
153       NULL,
154       0,
155       0,
156       NULL,
157       NULL
158     };
159
160     _type = g_type_register_static (G_TYPE_INTERFACE,
161         "GstURIHandler", &urihandler_info, 0);
162
163     GST_DEBUG_CATEGORY_INIT (gst_uri_handler_debug, "GST_URI", GST_DEBUG_BOLD,
164         "handling of URIs");
165     g_once_init_leave (&urihandler_type, _type);
166   }
167   return urihandler_type;
168 }
169
170 GQuark
171 gst_uri_error_quark (void)
172 {
173   return g_quark_from_static_string ("gst-uri-error-quark");
174 }
175
176 static const guchar acceptable[96] = {  /* X0   X1   X2   X3   X4   X5   X6   X7   X8   X9   XA   XB   XC   XD   XE   XF */
177   0x00, 0x3F, 0x20, 0x20, 0x20, 0x00, 0x2C, 0x3F, 0x3F, 0x3F, 0x3F, 0x22, 0x20, 0x3F, 0x3F, 0x1C,       /* 2X  !"#$%&'()*+,-./   */
178   0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x38, 0x20, 0x20, 0x2C, 0x20, 0x2C,       /* 3X 0123456789:;<=>?   */
179   0x30, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,       /* 4X @ABCDEFGHIJKLMNO   */
180   0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x3F,       /* 5X PQRSTUVWXYZ[\]^_   */
181   0x20, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,       /* 6X `abcdefghijklmno   */
182   0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x3F, 0x20        /* 7X pqrstuvwxyz{|}~DEL */
183 };
184
185 typedef enum
186 {
187   UNSAFE_ALL = 0x1,             /* Escape all unsafe characters   */
188   UNSAFE_ALLOW_PLUS = 0x2,      /* Allows '+'  */
189   UNSAFE_PATH = 0x4,            /* Allows '/' and '?' and '&' and '='  */
190   UNSAFE_DOS_PATH = 0x8,        /* Allows '/' and '?' and '&' and '=' and ':' */
191   UNSAFE_HOST = 0x10,           /* Allows '/' and ':' and '@' */
192   UNSAFE_SLASHES = 0x20         /* Allows all characters except for '/' and '%' */
193 } UnsafeCharacterSet;
194
195 #define HEX_ESCAPE '%'
196
197 /*  Escape undesirable characters using %
198  *  -------------------------------------
199  *
200  * This function takes a pointer to a string in which
201  * some characters may be unacceptable unescaped.
202  * It returns a string which has these characters
203  * represented by a '%' character followed by two hex digits.
204  *
205  * This routine returns a g_malloced string.
206  */
207
208 static const gchar hex[16] = "0123456789ABCDEF";
209
210 static gchar *
211 escape_string_internal (const gchar * string, UnsafeCharacterSet mask)
212 {
213 #define ACCEPTABLE_CHAR(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask))
214
215   const gchar *p;
216   gchar *q;
217   gchar *result;
218   guchar c;
219   gint unacceptable;
220   UnsafeCharacterSet use_mask;
221
222   g_return_val_if_fail (mask == UNSAFE_ALL
223       || mask == UNSAFE_ALLOW_PLUS
224       || mask == UNSAFE_PATH
225       || mask == UNSAFE_DOS_PATH
226       || mask == UNSAFE_HOST || mask == UNSAFE_SLASHES, NULL);
227
228   if (string == NULL) {
229     return NULL;
230   }
231
232   unacceptable = 0;
233   use_mask = mask;
234   for (p = string; *p != '\0'; p++) {
235     c = *p;
236     if (!ACCEPTABLE_CHAR (c)) {
237       unacceptable++;
238     }
239     if ((use_mask == UNSAFE_HOST) && (unacceptable || (c == '/'))) {
240       /* when escaping a host, if we hit something that needs to be escaped, or we finally
241        * hit a path separator, revert to path mode (the host segment of the url is over).
242        */
243       use_mask = UNSAFE_PATH;
244     }
245   }
246
247   result = g_malloc (p - string + unacceptable * 2 + 1);
248
249   use_mask = mask;
250   for (q = result, p = string; *p != '\0'; p++) {
251     c = *p;
252
253     if (!ACCEPTABLE_CHAR (c)) {
254       *q++ = HEX_ESCAPE;        /* means hex coming */
255       *q++ = hex[c >> 4];
256       *q++ = hex[c & 15];
257     } else {
258       *q++ = c;
259     }
260     if ((use_mask == UNSAFE_HOST) && (!ACCEPTABLE_CHAR (c) || (c == '/'))) {
261       use_mask = UNSAFE_PATH;
262     }
263   }
264
265   *q = '\0';
266
267   return result;
268 }
269
270 /* escape_string:
271  * @string: string to be escaped
272  *
273  * Escapes @string, replacing any and all special characters
274  * with equivalent escape sequences.
275  *
276  * Return value: a newly allocated string equivalent to @string
277  * but with all special characters escaped
278  **/
279 static gchar *
280 escape_string (const gchar * string)
281 {
282   return escape_string_internal (string, UNSAFE_ALL);
283 }
284
285 static int
286 hex_to_int (gchar c)
287 {
288   return c >= '0' && c <= '9' ? c - '0'
289       : c >= 'A' && c <= 'F' ? c - 'A' + 10
290       : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
291 }
292
293 static int
294 unescape_character (const char *scanner)
295 {
296   int first_digit;
297   int second_digit;
298
299   first_digit = hex_to_int (*scanner++);
300   if (first_digit < 0) {
301     return -1;
302   }
303
304   second_digit = hex_to_int (*scanner);
305   if (second_digit < 0) {
306     return -1;
307   }
308
309   return (first_digit << 4) | second_digit;
310 }
311
312 /* unescape_string:
313  * @escaped_string: an escaped URI, path, or other string
314  * @illegal_characters: a string containing a sequence of characters
315  * considered "illegal", '\0' is automatically in this list.
316  *
317  * Decodes escaped characters (i.e. PERCENTxx sequences) in @escaped_string.
318  * Characters are encoded in PERCENTxy form, where xy is the ASCII hex code
319  * for character 16x+y.
320  *
321  * Return value: (nullable): a newly allocated string with the
322  * unescaped equivalents, or %NULL if @escaped_string contained one of
323  * the characters in @illegal_characters.
324  **/
325 static char *
326 unescape_string (const gchar * escaped_string, const gchar * illegal_characters)
327 {
328   const gchar *in;
329   gchar *out, *result;
330   gint character;
331
332   if (escaped_string == NULL) {
333     return NULL;
334   }
335
336   result = g_malloc (strlen (escaped_string) + 1);
337
338   out = result;
339   for (in = escaped_string; *in != '\0'; in++) {
340     character = *in;
341     if (*in == HEX_ESCAPE) {
342       character = unescape_character (in + 1);
343
344       /* Check for an illegal character. We consider '\0' illegal here. */
345       if (character <= 0
346           || (illegal_characters != NULL
347               && strchr (illegal_characters, (char) character) != NULL)) {
348         g_free (result);
349         return NULL;
350       }
351       in += 2;
352     }
353     *out++ = (char) character;
354   }
355
356   *out = '\0';
357   g_assert ((gsize) (out - result) <= strlen (escaped_string));
358   return result;
359
360 }
361
362
363 static void
364 gst_uri_protocol_check_internal (const gchar * uri, gchar ** endptr)
365 {
366   gchar *check = (gchar *) uri;
367
368   g_assert (uri != NULL);
369   g_assert (endptr != NULL);
370
371   if (g_ascii_isalpha (*check)) {
372     check++;
373     while (g_ascii_isalnum (*check) || *check == '+'
374         || *check == '-' || *check == '.')
375       check++;
376   }
377
378   *endptr = check;
379 }
380
381 /**
382  * gst_uri_protocol_is_valid:
383  * @protocol: A string
384  *
385  * Tests if the given string is a valid protocol identifier. Protocols
386  * must consist of alphanumeric characters, '+', '-' and '.' and must
387  * start with a alphabetic character. See RFC 3986 Section 3.1.
388  *
389  * Returns: %TRUE if the string is a valid protocol identifier, %FALSE otherwise.
390  */
391 gboolean
392 gst_uri_protocol_is_valid (const gchar * protocol)
393 {
394   gchar *endptr;
395
396   g_return_val_if_fail (protocol != NULL, FALSE);
397
398   gst_uri_protocol_check_internal (protocol, &endptr);
399
400   return *endptr == '\0' && ((gsize) (endptr - protocol)) >= 2;
401 }
402
403 /**
404  * gst_uri_is_valid:
405  * @uri: A URI string
406  *
407  * Tests if the given string is a valid URI identifier. URIs start with a valid
408  * scheme followed by ":" and maybe a string identifying the location.
409  *
410  * Returns: %TRUE if the string is a valid URI
411  */
412 gboolean
413 gst_uri_is_valid (const gchar * uri)
414 {
415   gchar *endptr;
416
417   g_return_val_if_fail (uri != NULL, FALSE);
418
419   gst_uri_protocol_check_internal (uri, &endptr);
420
421   return *endptr == ':' && ((gsize) (endptr - uri)) >= 2;
422 }
423
424 /**
425  * gst_uri_get_protocol:
426  * @uri: A URI string
427  *
428  * Extracts the protocol out of a given valid URI. The returned string must be
429  * freed using g_free().
430  *
431  * Returns: The protocol for this URI.
432  */
433 gchar *
434 gst_uri_get_protocol (const gchar * uri)
435 {
436   gchar *colon;
437
438   g_return_val_if_fail (uri != NULL, NULL);
439   g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
440
441   colon = strstr (uri, ":");
442
443   return g_ascii_strdown (uri, colon - uri);
444 }
445
446 /**
447  * gst_uri_has_protocol:
448  * @uri: a URI string
449  * @protocol: a protocol string (e.g. "http")
450  *
451  * Checks if the protocol of a given valid URI matches @protocol.
452  *
453  * Returns: %TRUE if the protocol matches.
454  */
455 gboolean
456 gst_uri_has_protocol (const gchar * uri, const gchar * protocol)
457 {
458   gchar *colon;
459
460   g_return_val_if_fail (uri != NULL, FALSE);
461   g_return_val_if_fail (protocol != NULL, FALSE);
462   g_return_val_if_fail (gst_uri_is_valid (uri), FALSE);
463
464   colon = strstr (uri, ":");
465
466   if (colon == NULL)
467     return FALSE;
468
469   return (g_ascii_strncasecmp (uri, protocol, (gsize) (colon - uri)) == 0);
470 }
471
472 /**
473  * gst_uri_get_location:
474  * @uri: A URI string
475  *
476  * Extracts the location out of a given valid URI, ie. the protocol and "://"
477  * are stripped from the URI, which means that the location returned includes
478  * the hostname if one is specified. The returned string must be freed using
479  * g_free().
480  *
481  * Free-function: g_free
482  *
483  * Returns: (transfer full): the location for this URI. Returns %NULL if the
484  *     URI isn't valid. If the URI does not contain a location, an empty
485  *     string is returned.
486  */
487 gchar *
488 gst_uri_get_location (const gchar * uri)
489 {
490   const gchar *colon;
491   gchar *unescaped = NULL;
492
493   g_return_val_if_fail (uri != NULL, NULL);
494   g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
495
496   colon = strstr (uri, "://");
497   if (!colon)
498     return NULL;
499
500   unescaped = unescape_string (colon + 3, "/");
501
502   /* On Windows an URI might look like file:///c:/foo/bar.txt or
503    * file:///c|/foo/bar.txt (some Netscape versions) and we want to
504    * return c:/foo/bar.txt as location rather than /c:/foo/bar.txt.
505    * Can't use g_filename_from_uri() here because it will only handle the
506    * file:// protocol */
507 #ifdef G_OS_WIN32
508   if (unescaped != NULL && unescaped[0] == '/' &&
509       g_ascii_isalpha (unescaped[1]) &&
510       (unescaped[2] == ':' || unescaped[2] == '|')) {
511     unescaped[2] = ':';
512     memmove (unescaped, unescaped + 1, strlen (unescaped + 1) + 1);
513   }
514 #endif
515
516   GST_LOG ("extracted location '%s' from URI '%s'", GST_STR_NULL (unescaped),
517       uri);
518   return unescaped;
519 }
520
521 /**
522  * gst_uri_construct:
523  * @protocol: Protocol for URI
524  * @location: (transfer none): Location for URI
525  *
526  * Constructs a URI for a given valid protocol and location.
527  *
528  * Free-function: g_free
529  *
530  * Returns: (transfer full): a new string for this URI. Returns %NULL if the
531  *     given URI protocol is not valid, or the given location is %NULL.
532  */
533 gchar *
534 gst_uri_construct (const gchar * protocol, const gchar * location)
535 {
536   char *escaped, *proto_lowercase;
537   char *retval;
538
539   g_return_val_if_fail (gst_uri_protocol_is_valid (protocol), NULL);
540   g_return_val_if_fail (location != NULL, NULL);
541
542   proto_lowercase = g_ascii_strdown (protocol, -1);
543   escaped = escape_string (location);
544   retval = g_strdup_printf ("%s://%s", proto_lowercase, escaped);
545   g_free (escaped);
546   g_free (proto_lowercase);
547
548   return retval;
549 }
550
551 typedef struct
552 {
553   GstURIType type;
554   const gchar *protocol;
555 }
556 SearchEntry;
557
558 static gboolean
559 search_by_entry (GstPluginFeature * feature, gpointer search_entry)
560 {
561   const gchar *const *protocols;
562   GstElementFactory *factory;
563   SearchEntry *entry = (SearchEntry *) search_entry;
564
565   if (!GST_IS_ELEMENT_FACTORY (feature))
566     return FALSE;
567   factory = GST_ELEMENT_FACTORY_CAST (feature);
568
569   if (factory->uri_type != entry->type)
570     return FALSE;
571
572   protocols = gst_element_factory_get_uri_protocols (factory);
573
574   if (protocols == NULL) {
575     g_warning ("Factory '%s' implements GstUriHandler interface but returned "
576         "no supported protocols!", gst_plugin_feature_get_name (feature));
577     return FALSE;
578   }
579
580   while (*protocols != NULL) {
581     if (g_ascii_strcasecmp (*protocols, entry->protocol) == 0)
582       return TRUE;
583     protocols++;
584   }
585   return FALSE;
586 }
587
588 static gint
589 sort_by_rank (GstPluginFeature * first, GstPluginFeature * second)
590 {
591   return gst_plugin_feature_get_rank (second) -
592       gst_plugin_feature_get_rank (first);
593 }
594
595 static GList *
596 get_element_factories_from_uri_protocol (const GstURIType type,
597     const gchar * protocol)
598 {
599   GList *possibilities;
600   SearchEntry entry;
601
602   g_return_val_if_fail (protocol, NULL);
603
604   entry.type = type;
605   entry.protocol = protocol;
606   possibilities = gst_registry_feature_filter (gst_registry_get (),
607       search_by_entry, FALSE, &entry);
608
609   return possibilities;
610 }
611
612 /**
613  * gst_uri_protocol_is_supported:
614  * @type: Whether to check for a source or a sink
615  * @protocol: Protocol that should be checked for (e.g. "http" or "smb")
616  *
617  * Checks if an element exists that supports the given URI protocol. Note
618  * that a positive return value does not imply that a subsequent call to
619  * gst_element_make_from_uri() is guaranteed to work.
620  *
621  * Returns: %TRUE
622 */
623 gboolean
624 gst_uri_protocol_is_supported (const GstURIType type, const gchar * protocol)
625 {
626   GList *possibilities;
627
628   g_return_val_if_fail (protocol, FALSE);
629
630   possibilities = get_element_factories_from_uri_protocol (type, protocol);
631
632   if (possibilities) {
633     g_list_free (possibilities);
634     return TRUE;
635   } else
636     return FALSE;
637 }
638
639 /**
640  * gst_element_make_from_uri:
641  * @type: Whether to create a source or a sink
642  * @uri: URI to create an element for
643  * @elementname: (allow-none): Name of created element, can be %NULL.
644  * @error: (allow-none): address where to store error information, or %NULL.
645  *
646  * Creates an element for handling the given URI.
647  *
648  * Returns: (transfer floating): a new element or %NULL if none could be created
649  */
650 GstElement *
651 gst_element_make_from_uri (const GstURIType type, const gchar * uri,
652     const gchar * elementname, GError ** error)
653 {
654   GList *possibilities, *walk;
655   gchar *protocol;
656   GstElement *ret = NULL;
657
658   g_return_val_if_fail (gst_is_initialized (), NULL);
659   g_return_val_if_fail (GST_URI_TYPE_IS_VALID (type), NULL);
660   g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
661   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
662
663   GST_DEBUG ("type:%d, uri:%s, elementname:%s", type, uri, elementname);
664
665   protocol = gst_uri_get_protocol (uri);
666   possibilities = get_element_factories_from_uri_protocol (type, protocol);
667
668   if (!possibilities) {
669     GST_DEBUG ("No %s for URI '%s'", type == GST_URI_SINK ? "sink" : "source",
670         uri);
671     /* The error message isn't great, but we don't expect applications to
672      * show that error to users, but call the missing plugins functions */
673     g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
674         _("No URI handler for the %s protocol found"), protocol);
675     g_free (protocol);
676     return NULL;
677   }
678   g_free (protocol);
679
680   possibilities = g_list_sort (possibilities, (GCompareFunc) sort_by_rank);
681   walk = possibilities;
682   while (walk) {
683     GstElementFactory *factory = walk->data;
684     GError *uri_err = NULL;
685
686     ret = gst_element_factory_create (factory, elementname);
687     if (ret != NULL) {
688       GstURIHandler *handler = GST_URI_HANDLER (ret);
689
690       if (gst_uri_handler_set_uri (handler, uri, &uri_err))
691         break;
692
693       GST_WARNING ("%s didn't accept URI '%s': %s", GST_OBJECT_NAME (ret), uri,
694           uri_err->message);
695
696       if (error != NULL && *error == NULL)
697         g_propagate_error (error, uri_err);
698       else
699         g_error_free (uri_err);
700
701       gst_object_unref (ret);
702       ret = NULL;
703     }
704     walk = walk->next;
705   }
706   gst_plugin_feature_list_free (possibilities);
707
708   GST_LOG_OBJECT (ret, "created %s for URL '%s'",
709       type == GST_URI_SINK ? "sink" : "source", uri);
710
711   /* if the first handler didn't work, but we found another one that works */
712   if (ret != NULL)
713     g_clear_error (error);
714
715   return ret;
716 }
717
718 /**
719  * gst_uri_handler_get_uri_type:
720  * @handler: A #GstURIHandler.
721  *
722  * Gets the type of the given URI handler
723  *
724  * Returns: the #GstURIType of the URI handler.
725  * Returns #GST_URI_UNKNOWN if the @handler isn't implemented correctly.
726  */
727 GstURIType
728 gst_uri_handler_get_uri_type (GstURIHandler * handler)
729 {
730   GstURIHandlerInterface *iface;
731   GstURIType ret;
732
733   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), GST_URI_UNKNOWN);
734
735   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
736   g_return_val_if_fail (iface != NULL, GST_URI_UNKNOWN);
737   g_return_val_if_fail (iface->get_type != NULL, GST_URI_UNKNOWN);
738
739   ret = iface->get_type (G_OBJECT_TYPE (handler));
740   g_return_val_if_fail (GST_URI_TYPE_IS_VALID (ret), GST_URI_UNKNOWN);
741
742   return ret;
743 }
744
745 /**
746  * gst_uri_handler_get_protocols:
747  * @handler: A #GstURIHandler.
748  *
749  * Gets the list of protocols supported by @handler. This list may not be
750  * modified.
751  *
752  * Returns: (transfer none) (element-type utf8) (nullable): the
753  *     supported protocols.  Returns %NULL if the @handler isn't
754  *     implemented properly, or the @handler doesn't support any
755  *     protocols.
756  */
757 const gchar *const *
758 gst_uri_handler_get_protocols (GstURIHandler * handler)
759 {
760   GstURIHandlerInterface *iface;
761   const gchar *const *ret;
762
763   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
764
765   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
766   g_return_val_if_fail (iface != NULL, NULL);
767   g_return_val_if_fail (iface->get_protocols != NULL, NULL);
768
769   ret = iface->get_protocols (G_OBJECT_TYPE (handler));
770   g_return_val_if_fail (ret != NULL, NULL);
771
772   return ret;
773 }
774
775 /**
776  * gst_uri_handler_get_uri:
777  * @handler: A #GstURIHandler
778  *
779  * Gets the currently handled URI.
780  *
781  * Returns: (transfer full) (nullable): the URI currently handled by
782  *   the @handler.  Returns %NULL if there are no URI currently
783  *   handled. The returned string must be freed with g_free() when no
784  *   longer needed.
785  */
786 gchar *
787 gst_uri_handler_get_uri (GstURIHandler * handler)
788 {
789   GstURIHandlerInterface *iface;
790   gchar *ret;
791
792   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
793
794   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
795   g_return_val_if_fail (iface != NULL, NULL);
796   g_return_val_if_fail (iface->get_uri != NULL, NULL);
797   ret = iface->get_uri (handler);
798   if (ret != NULL)
799     g_return_val_if_fail (gst_uri_is_valid (ret), NULL);
800
801   return ret;
802 }
803
804 /**
805  * gst_uri_handler_set_uri:
806  * @handler: A #GstURIHandler
807  * @uri: URI to set
808  * @error: (allow-none): address where to store a #GError in case of
809  *    an error, or %NULL
810  *
811  * Tries to set the URI of the given handler.
812  *
813  * Returns: %TRUE if the URI was set successfully, else %FALSE.
814  */
815 gboolean
816 gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri,
817     GError ** error)
818 {
819   GstURIHandlerInterface *iface;
820   gboolean ret;
821   gchar *protocol;
822
823   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), FALSE);
824   g_return_val_if_fail (gst_uri_is_valid (uri), FALSE);
825   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
826
827   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
828   g_return_val_if_fail (iface != NULL, FALSE);
829   g_return_val_if_fail (iface->set_uri != NULL, FALSE);
830
831   protocol = gst_uri_get_protocol (uri);
832
833   if (iface->get_protocols) {
834     const gchar *const *protocols;
835     const gchar *const *p;
836     gboolean found_protocol = FALSE;
837
838     protocols = iface->get_protocols (G_OBJECT_TYPE (handler));
839     if (protocols != NULL) {
840       for (p = protocols; *p != NULL; ++p) {
841         if (g_ascii_strcasecmp (protocol, *p) == 0) {
842           found_protocol = TRUE;
843           break;
844         }
845       }
846
847       if (!found_protocol) {
848         g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
849             _("URI scheme '%s' not supported"), protocol);
850         g_free (protocol);
851         return FALSE;
852       }
853     }
854   }
855
856   ret = iface->set_uri (handler, uri, error);
857
858   g_free (protocol);
859
860   return ret;
861 }
862
863 static gchar *
864 gst_file_utils_canonicalise_path (const gchar * path)
865 {
866   gchar **parts, **p, *clean_path;
867
868 #ifdef G_OS_WIN32
869   {
870     GST_WARNING ("FIXME: canonicalise win32 path");
871     return g_strdup (path);
872   }
873 #endif
874
875   parts = g_strsplit (path, "/", -1);
876
877   p = parts;
878   while (*p != NULL) {
879     if (strcmp (*p, ".") == 0) {
880       /* just move all following parts on top of this, incl. NUL terminator */
881       g_free (*p);
882       memmove (p, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *));
883       /* re-check the new current part again in the next iteration */
884       continue;
885     } else if (strcmp (*p, "..") == 0 && p > parts) {
886       /* just move all following parts on top of the previous part, incl.
887        * NUL terminator */
888       g_free (*(p - 1));
889       g_free (*p);
890       memmove (p - 1, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *));
891       /* re-check the new current part again in the next iteration */
892       --p;
893       continue;
894     }
895     ++p;
896   }
897   if (*path == '/') {
898     guint num_parts;
899
900     num_parts = g_strv_length (parts) + 1;      /* incl. terminator */
901     parts = g_renew (gchar *, parts, num_parts + 1);
902     memmove (parts + 1, parts, num_parts * sizeof (gchar *));
903     parts[0] = g_strdup ("/");
904   }
905
906   clean_path = g_build_filenamev (parts);
907   g_strfreev (parts);
908   return clean_path;
909 }
910
911 static gboolean
912 file_path_contains_relatives (const gchar * path)
913 {
914   return (strstr (path, "/./") != NULL || strstr (path, "/../") != NULL ||
915       strstr (path, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) != NULL ||
916       strstr (path, G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) != NULL);
917 }
918
919 /**
920  * gst_filename_to_uri:
921  * @filename: absolute or relative file name path
922  * @error: pointer to error, or %NULL
923  *
924  * Similar to g_filename_to_uri(), but attempts to handle relative file paths
925  * as well. Before converting @filename into an URI, it will be prefixed by
926  * the current working directory if it is a relative path, and then the path
927  * will be canonicalised so that it doesn't contain any './' or '../' segments.
928  *
929  * On Windows #filename should be in UTF-8 encoding.
930  *
931  * Returns: newly-allocated URI string, or NULL on error. The caller must
932  *   free the URI string with g_free() when no longer needed.
933  */
934 gchar *
935 gst_filename_to_uri (const gchar * filename, GError ** error)
936 {
937   gchar *abs_location = NULL;
938   gchar *uri, *abs_clean;
939
940   g_return_val_if_fail (filename != NULL, NULL);
941   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
942
943   if (g_path_is_absolute (filename)) {
944     if (!file_path_contains_relatives (filename)) {
945       uri = g_filename_to_uri (filename, NULL, error);
946       goto beach;
947     }
948
949     abs_location = g_strdup (filename);
950   } else {
951     gchar *cwd;
952
953     cwd = g_get_current_dir ();
954     abs_location = g_build_filename (cwd, filename, NULL);
955     g_free (cwd);
956
957     if (!file_path_contains_relatives (abs_location)) {
958       uri = g_filename_to_uri (abs_location, NULL, error);
959       goto beach;
960     }
961   }
962
963   /* path is now absolute, but contains '.' or '..' */
964   abs_clean = gst_file_utils_canonicalise_path (abs_location);
965   GST_LOG ("'%s' -> '%s' -> '%s'", filename, abs_location, abs_clean);
966   uri = g_filename_to_uri (abs_clean, NULL, error);
967   g_free (abs_clean);
968
969 beach:
970
971   g_free (abs_location);
972   GST_DEBUG ("'%s' -> '%s'", filename, uri);
973   return uri;
974 }
975
976 /****************************************************************************
977  * GstUri - GstMiniObject to parse and merge URIs according to IETF RFC 3986
978  ****************************************************************************/
979
980 /**
981  * SECTION:gsturi
982  * @short_description: URI parsing and manipulation.
983  *
984  * A #GstUri object can be used to parse and split a URI string into its
985  * constituant parts. Two #GstUri objects can be joined to make a new #GstUri
986  * using the algorithm described in RFC3986.
987  */
988
989 /* Definition for GstUri object */
990 struct _GstUri
991 {
992   /*< private > */
993   GstMiniObject mini_object;
994   gchar *scheme;
995   gchar *userinfo;
996   gchar *host;
997   guint port;
998   GList *path;
999   GHashTable *query;
1000   gchar *fragment;
1001 };
1002
1003 GST_DEFINE_MINI_OBJECT_TYPE (GstUri, gst_uri);
1004
1005 static GstUri *_gst_uri_copy (const GstUri * uri);
1006 static void _gst_uri_free (GstUri * uri);
1007 static GstUri *_gst_uri_new (void);
1008 static GList *_remove_dot_segments (GList * path);
1009
1010 /* private GstUri functions */
1011
1012 static GstUri *
1013 _gst_uri_new (void)
1014 {
1015   GstUri *uri;
1016   uri = GST_URI_CAST (g_slice_new0 (GstUri));
1017
1018   if (uri)
1019     gst_mini_object_init (GST_MINI_OBJECT_CAST (uri), 0, gst_uri_get_type (),
1020         (GstMiniObjectCopyFunction) _gst_uri_copy, NULL,
1021         (GstMiniObjectFreeFunction) _gst_uri_free);
1022
1023   return uri;
1024 }
1025
1026 static void
1027 _gst_uri_free (GstUri * uri)
1028 {
1029   g_return_if_fail (GST_IS_URI (uri));
1030
1031   g_free (uri->scheme);
1032   g_free (uri->userinfo);
1033   g_free (uri->host);
1034   g_list_free_full (uri->path, g_free);
1035   if (uri->query)
1036     g_hash_table_unref (uri->query);
1037   g_free (uri->fragment);
1038
1039   g_slice_free1 (sizeof (*uri), uri);
1040 }
1041
1042 static GHashTable *
1043 _gst_uri_copy_query_table (GHashTable * orig)
1044 {
1045   GHashTable *new = NULL;
1046
1047   if (orig != NULL) {
1048     GHashTableIter iter;
1049     gpointer key, value;
1050     new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1051     g_hash_table_iter_init (&iter, orig);
1052     while (g_hash_table_iter_next (&iter, &key, &value)) {
1053       g_hash_table_insert (new, g_strdup (key), g_strdup (value));
1054     }
1055   }
1056
1057   return new;
1058 }
1059
1060 static GstUri *
1061 _gst_uri_copy (const GstUri * orig_uri)
1062 {
1063   GstUri *new_uri;
1064
1065   g_return_val_if_fail (GST_IS_URI (orig_uri), NULL);
1066
1067   new_uri = _gst_uri_new ();
1068
1069   if (new_uri) {
1070     new_uri->scheme = g_strdup (orig_uri->scheme);
1071     new_uri->userinfo = g_strdup (orig_uri->userinfo);
1072     new_uri->host = g_strdup (orig_uri->host);
1073     new_uri->port = orig_uri->port;
1074     new_uri->path = g_list_copy_deep (orig_uri->path, (GCopyFunc) g_strdup,
1075         NULL);
1076     new_uri->query = _gst_uri_copy_query_table (orig_uri->query);
1077     new_uri->fragment = g_strdup (orig_uri->fragment);
1078   }
1079
1080   return new_uri;
1081 }
1082
1083 /*
1084  * _gst_uri_compare_lists:
1085  *
1086  * Compare two lists for equality. This compares the two lists, item for item,
1087  * comparing items in the same position in the two lists. If @first is
1088  * considered less than @second the result will be negative. If @first is
1089  * considered to be more than @second then the result will be positive. If the
1090  * lists are considered to be equal then the result will be 0. If two lists
1091  * have the same items, but one list is shorter than the other, then the
1092  * shorter list is considered to be less than the longer list.
1093  */
1094 static gint
1095 _gst_uri_compare_lists (GList * first, GList * second, GCompareFunc cmp_fn)
1096 {
1097   GList *itr1, *itr2;
1098   gint result;
1099
1100   for (itr1 = first, itr2 = second;
1101       itr1 != NULL || itr2 != NULL; itr1 = itr1->next, itr2 = itr2->next) {
1102     if (itr1 == NULL)
1103       return -1;
1104     if (itr2 == NULL)
1105       return 1;
1106     result = cmp_fn (itr1->data, itr2->data);
1107     if (result != 0)
1108       return result;
1109   }
1110   return 0;
1111 }
1112
1113 typedef enum
1114 {
1115   _GST_URI_NORMALIZE_LOWERCASE = 1,
1116   _GST_URI_NORMALIZE_UPPERCASE = 2
1117 } _GstUriNormalizations;
1118
1119 /*
1120  * Find the first character that hasn't been normalized according to the @flags.
1121  */
1122 static gchar *
1123 _gst_uri_first_non_normalized_char (gchar * str, guint flags)
1124 {
1125   gchar *pos;
1126
1127   if (str == NULL)
1128     return NULL;
1129
1130   for (pos = str; *pos; pos++) {
1131     if ((flags & _GST_URI_NORMALIZE_UPPERCASE) && g_ascii_islower (*pos))
1132       return pos;
1133     if ((flags & _GST_URI_NORMALIZE_LOWERCASE) && g_ascii_isupper (*pos))
1134       return pos;
1135   }
1136   return NULL;
1137 }
1138
1139 static gboolean
1140 _gst_uri_normalize_lowercase (gchar * str)
1141 {
1142   gchar *pos;
1143   gboolean ret = FALSE;
1144
1145   for (pos = _gst_uri_first_non_normalized_char (str,
1146           _GST_URI_NORMALIZE_LOWERCASE);
1147       pos != NULL;
1148       pos = _gst_uri_first_non_normalized_char (pos + 1,
1149           _GST_URI_NORMALIZE_LOWERCASE)) {
1150     *pos = g_ascii_tolower (*pos);
1151     ret = TRUE;
1152   }
1153
1154   return ret;
1155 }
1156
1157 #define _gst_uri_normalize_scheme _gst_uri_normalize_lowercase
1158 #define _gst_uri_normalize_hostname _gst_uri_normalize_lowercase
1159
1160 static gboolean
1161 _gst_uri_normalize_path (GList ** path)
1162 {
1163   GList *new_path;
1164
1165   new_path = _remove_dot_segments (*path);
1166   if (_gst_uri_compare_lists (new_path, *path, (GCompareFunc) g_strcmp0) != 0) {
1167     g_list_free_full (*path, g_free);
1168     *path = new_path;
1169     return TRUE;
1170   }
1171   g_list_free_full (new_path, g_free);
1172
1173   return FALSE;
1174 }
1175
1176 static gboolean
1177 _gst_uri_normalize_str_noop (gchar * str)
1178 {
1179   return FALSE;
1180 }
1181
1182 static gboolean
1183 _gst_uri_normalize_table_noop (GHashTable * table)
1184 {
1185   return FALSE;
1186 }
1187
1188 #define _gst_uri_normalize_userinfo _gst_uri_normalize_str_noop
1189 #define _gst_uri_normalize_query _gst_uri_normalize_table_noop
1190 #define _gst_uri_normalize_fragment _gst_uri_normalize_str_noop
1191
1192 /* RFC 3986 functions */
1193
1194 static GList *
1195 _merge (GList * base, GList * path)
1196 {
1197   GList *ret, *path_copy, *last;
1198
1199   path_copy = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL);
1200   /* if base is NULL make path absolute */
1201   if (base == NULL) {
1202     if (path_copy != NULL && path_copy->data != NULL) {
1203       path_copy = g_list_prepend (path_copy, NULL);
1204     }
1205     return path_copy;
1206   }
1207
1208   ret = g_list_copy_deep (base, (GCopyFunc) g_strdup, NULL);
1209   last = g_list_last (ret);
1210   ret = g_list_remove_link (ret, last);
1211   g_list_free_full (last, g_free);
1212   ret = g_list_concat (ret, path_copy);
1213
1214   return ret;
1215 }
1216
1217 static GList *
1218 _remove_dot_segments (GList * path)
1219 {
1220   GList *out, *elem, *next;
1221
1222   if (path == NULL)
1223     return NULL;
1224
1225   out = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL);
1226
1227   for (elem = out; elem; elem = next) {
1228     next = elem->next;
1229     if (elem->data == NULL && elem != out && next != NULL) {
1230       out = g_list_delete_link (out, elem);
1231     } else if (g_strcmp0 (elem->data, ".") == 0) {
1232       g_free (elem->data);
1233       out = g_list_delete_link (out, elem);
1234     } else if (g_strcmp0 (elem->data, "..") == 0) {
1235       GList *prev = g_list_previous (elem);
1236       if (prev && (prev != out || prev->data != NULL)) {
1237         g_free (prev->data);
1238         out = g_list_delete_link (out, prev);
1239       }
1240       g_free (elem->data);
1241       if (next != NULL) {
1242         out = g_list_delete_link (out, elem);
1243       } else {
1244         /* path ends in '/..' We need to keep the last '/' */
1245         elem->data = NULL;
1246       }
1247     }
1248   }
1249
1250   return out;
1251 }
1252
1253 static gchar *
1254 _gst_uri_escape_userinfo (const gchar * userinfo)
1255 {
1256   return g_uri_escape_string (userinfo,
1257       G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, FALSE);
1258 }
1259
1260 static gchar *
1261 _gst_uri_escape_host (const gchar * host)
1262 {
1263   return g_uri_escape_string (host,
1264       G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, FALSE);
1265 }
1266
1267 static gchar *
1268 _gst_uri_escape_host_colon (const gchar * host)
1269 {
1270   return g_uri_escape_string (host,
1271       G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS ":", FALSE);
1272 }
1273
1274 static gchar *
1275 _gst_uri_escape_path_segment (const gchar * segment)
1276 {
1277   return g_uri_escape_string (segment,
1278       G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT, FALSE);
1279 }
1280
1281 static gchar *
1282 _gst_uri_escape_http_query_element (const gchar * element)
1283 {
1284   gchar *ret, *c;
1285
1286   ret = g_uri_escape_string (element, "!$'()*,;:@/? ", FALSE);
1287   for (c = ret; *c; c++)
1288     if (*c == ' ')
1289       *c = '+';
1290   return ret;
1291 }
1292
1293 static gchar *
1294 _gst_uri_escape_fragment (const gchar * fragment)
1295 {
1296   return g_uri_escape_string (fragment,
1297       G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?", FALSE);
1298 }
1299
1300 static GList *
1301 _gst_uri_string_to_list (const gchar * str, const gchar * sep, gboolean convert,
1302     gboolean unescape)
1303 {
1304   GList *new_list = NULL;
1305
1306   if (str) {
1307     guint pct_sep_len = 0;
1308     gchar *pct_sep = NULL;
1309     gchar **split_str;
1310
1311     if (convert && !unescape) {
1312       pct_sep = g_strdup_printf ("%%%2.2X", (guint) (*sep));
1313       pct_sep_len = 3;
1314     }
1315
1316     split_str = g_strsplit (str, sep, -1);
1317     if (split_str) {
1318       gchar **next_elem;
1319       for (next_elem = split_str; *next_elem; next_elem += 1) {
1320         gchar *elem = *next_elem;
1321         if (*elem == '\0') {
1322           new_list = g_list_append (new_list, NULL);
1323         } else {
1324           if (convert && !unescape) {
1325             gchar *next_sep;
1326             for (next_sep = strcasestr (elem, pct_sep); next_sep;
1327                 next_sep = strcasestr (next_sep + 1, pct_sep)) {
1328               *next_sep = *sep;
1329               memmove (next_sep + 1, next_sep + pct_sep_len,
1330                   strlen (next_sep + pct_sep_len) + 1);
1331             }
1332           }
1333           if (unescape) {
1334             *next_elem = g_uri_unescape_string (elem, NULL);
1335             g_free (elem);
1336             elem = *next_elem;
1337           }
1338           new_list = g_list_append (new_list, g_strdup (elem));
1339         }
1340       }
1341     }
1342     g_strfreev (split_str);
1343     if (convert && !unescape)
1344       g_free (pct_sep);
1345   }
1346
1347   return new_list;
1348 }
1349
1350 static GHashTable *
1351 _gst_uri_string_to_table (const gchar * str, const gchar * part_sep,
1352     const gchar * kv_sep, gboolean convert, gboolean unescape)
1353 {
1354   GHashTable *new_table = NULL;
1355
1356   if (str) {
1357     gchar *pct_part_sep = NULL, *pct_kv_sep = NULL;
1358     gchar **split_parts;
1359
1360     new_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1361
1362     if (convert && !unescape) {
1363       pct_part_sep = g_strdup_printf ("%%%2.2X", (guint) (*part_sep));
1364       pct_kv_sep = g_strdup_printf ("%%%2.2X", (guint) (*kv_sep));
1365     }
1366
1367     split_parts = g_strsplit (str, part_sep, -1);
1368     if (split_parts) {
1369       gchar **next_part;
1370       for (next_part = split_parts; *next_part; next_part += 1) {
1371         gchar *part = *next_part;
1372         gchar *kv_sep_pos;
1373         gchar *key, *value;
1374         /* if we are converting percent encoded versions of separators then
1375          *  substitute the part separator now. */
1376         if (convert && !unescape) {
1377           gchar *next_sep;
1378           for (next_sep = strcasestr (part, pct_part_sep); next_sep;
1379               next_sep = strcasestr (next_sep + 1, pct_part_sep)) {
1380             *next_sep = *part_sep;
1381             memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1);
1382           }
1383         }
1384         /* find the key/value separator within the part */
1385         kv_sep_pos = g_strstr_len (part, -1, kv_sep);
1386         if (kv_sep_pos == NULL) {
1387           if (unescape) {
1388             key = g_uri_unescape_string (part, NULL);
1389           } else {
1390             key = g_strdup (part);
1391           }
1392           value = NULL;
1393         } else {
1394           if (unescape) {
1395             key = g_uri_unescape_segment (part, kv_sep_pos, NULL);
1396             value = g_uri_unescape_string (kv_sep_pos + 1, NULL);
1397           } else {
1398             key = g_strndup (part, kv_sep_pos - part);
1399             value = g_strdup (kv_sep_pos + 1);
1400           }
1401         }
1402         /* if we are converting percent encoded versions of separators then
1403          *  substitute the key/value separator in both key and value now. */
1404         if (convert && !unescape) {
1405           gchar *next_sep;
1406           for (next_sep = strcasestr (key, pct_kv_sep); next_sep;
1407               next_sep = strcasestr (next_sep + 1, pct_kv_sep)) {
1408             *next_sep = *kv_sep;
1409             memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1);
1410           }
1411           if (value) {
1412             for (next_sep = strcasestr (value, pct_kv_sep); next_sep;
1413                 next_sep = strcasestr (next_sep + 1, pct_kv_sep)) {
1414               *next_sep = *kv_sep;
1415               memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1);
1416             }
1417           }
1418         }
1419         /* add value to the table */
1420         g_hash_table_insert (new_table, key, value);
1421       }
1422     }
1423     /* tidy up */
1424     g_strfreev (split_parts);
1425     if (convert && !unescape) {
1426       g_free (pct_part_sep);
1427       g_free (pct_kv_sep);
1428     }
1429   }
1430
1431   return new_table;
1432 }
1433
1434
1435 /*
1436  * Method definitions.
1437  */
1438
1439 /**
1440  * gst_uri_new:
1441  * @scheme: (nullable): The scheme for the new URI.
1442  * @userinfo: (nullable): The user-info for the new URI.
1443  * @host: (nullable): The host name for the new URI.
1444  * @port: The port number for the new URI or %GST_URI_NO_PORT.
1445  * @path: (nullable): The path for the new URI with '/' separating path
1446  *                      elements.
1447  * @query: (nullable): The query string for the new URI with '&' separating
1448  *                       query elements. Elements containing '&' characters
1449  *                       should encode them as "&percnt;26".
1450  * @fragment: (nullable): The fragment name for the new URI.
1451  *
1452  * Creates a new #GstUri object with the given URI parts. The path and query
1453  * strings will be broken down into their elements. All strings should not be
1454  * escaped except where indicated.
1455  *
1456  * Returns: (transfer full): A new #GstUri object.
1457  *
1458  * Since: 1.6
1459  */
1460 GstUri *
1461 gst_uri_new (const gchar * scheme, const gchar * userinfo, const gchar * host,
1462     guint port, const gchar * path, const gchar * query, const gchar * fragment)
1463 {
1464   GstUri *new_uri;
1465
1466   new_uri = _gst_uri_new ();
1467   if (new_uri) {
1468     new_uri->scheme = g_strdup (scheme);
1469     new_uri->userinfo = g_strdup (userinfo);
1470     new_uri->host = g_strdup (host);
1471     new_uri->port = port;
1472     new_uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE);
1473     new_uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, FALSE);
1474     new_uri->fragment = g_strdup (fragment);
1475   }
1476
1477   return new_uri;
1478 }
1479
1480 /**
1481  * gst_uri_new_with_base:
1482  * @base: (transfer none)(nullable): The base URI to join the new URI to.
1483  * @scheme: (nullable): The scheme for the new URI.
1484  * @userinfo: (nullable): The user-info for the new URI.
1485  * @host: (nullable): The host name for the new URI.
1486  * @port: The port number for the new URI or %GST_URI_NO_PORT.
1487  * @path: (nullable): The path for the new URI with '/' separating path
1488  *                      elements.
1489  * @query: (nullable): The query string for the new URI with '&' separating
1490  *                       query elements. Elements containing '&' characters
1491  *                       should encode them as "&percnt;26".
1492  * @fragment: (nullable): The fragment name for the new URI.
1493  *
1494  * Like gst_uri_new(), but joins the new URI onto a base URI.
1495  *
1496  * Returns: (transfer full): The new URI joined onto @base.
1497  *
1498  * Since: 1.6
1499  */
1500 GstUri *
1501 gst_uri_new_with_base (GstUri * base, const gchar * scheme,
1502     const gchar * userinfo, const gchar * host, guint port, const gchar * path,
1503     const gchar * query, const gchar * fragment)
1504 {
1505   GstUri *new_rel_uri;
1506   GstUri *new_uri;
1507
1508   g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL);
1509
1510   new_rel_uri = gst_uri_new (scheme, userinfo, host, port, path, query,
1511       fragment);
1512   new_uri = gst_uri_join (base, new_rel_uri);
1513   gst_uri_unref (new_rel_uri);
1514
1515   return new_uri;
1516 }
1517
1518 /**
1519  * gst_uri_from_string:
1520  * @uri: The URI string to parse.
1521  *
1522  * Parses a URI string into a new #GstUri object. Will return NULL if the URI
1523  * cannot be parsed.
1524  *
1525  * Returns: (transfer full)(nullable): A new #GstUri object, or NULL.
1526  *
1527  * Since: 1.6
1528  */
1529 GstUri *
1530 gst_uri_from_string (const gchar * uri)
1531 {
1532   const gchar *orig_uri = uri;
1533   GstUri *uri_obj;
1534
1535   uri_obj = _gst_uri_new ();
1536
1537   if (uri_obj && uri != NULL) {
1538     int i = 0;
1539
1540     /* be helpful and skip initial white space */
1541     while (*uri == '\v' || g_ascii_isspace (*uri))
1542       uri++;
1543
1544     if (g_ascii_isalpha (uri[i])) {
1545       /* find end of scheme name */
1546       i++;
1547       while (g_ascii_isalnum (uri[i]) || uri[i] == '+' || uri[i] == '-' ||
1548           uri[i] == '.')
1549         i++;
1550     }
1551     if (i > 0 && uri[i] == ':') {
1552       /* get scheme */
1553       uri_obj->scheme = g_strndup (uri, i);
1554       uri += i + 1;
1555     }
1556     if (uri[0] == '/' && uri[1] == '/') {
1557       const gchar *eoa, *eoui, *eoh, *reoh;
1558       /* get authority [userinfo@]host[:port] */
1559       uri += 2;
1560       /* find end of authority */
1561       eoa = uri + strcspn (uri, "/?#");
1562
1563       /* find end of userinfo */
1564       eoui = strchr (uri, '@');
1565       if (eoui != NULL && eoui < eoa) {
1566         uri_obj->userinfo = g_uri_unescape_segment (uri, eoui, NULL);
1567         uri = eoui + 1;
1568       }
1569       /* find end of host */
1570       if (uri[0] == '[') {
1571         eoh = strchr (uri, ']');
1572         if (eoh == NULL || eoh > eoa) {
1573           GST_DEBUG ("Unable to parse the host part of the URI '%s'.",
1574               orig_uri);
1575           _gst_uri_free (uri_obj);
1576           return NULL;
1577         }
1578         reoh = eoh + 1;
1579         uri++;
1580       } else {
1581         reoh = eoh = strchr (uri, ':');
1582         if (eoh == NULL || eoh > eoa)
1583           reoh = eoh = eoa;
1584       }
1585       /* don't capture empty host strings */
1586       if (eoh != uri)
1587         uri_obj->host = g_uri_unescape_segment (uri, eoh, NULL);
1588
1589       uri = reoh;
1590       if (uri < eoa) {
1591         /* if port number is malformed then we can't parse this */
1592         if (uri[0] != ':' || strspn (uri + 1, "0123456789") != eoa - uri - 1) {
1593           GST_DEBUG ("Unable to parse host/port part of the URI '%s'.",
1594               orig_uri);
1595           _gst_uri_free (uri_obj);
1596           return NULL;
1597         }
1598         /* otherwise treat port as unsigned decimal number */
1599         uri++;
1600         while (uri < eoa) {
1601           uri_obj->port = uri_obj->port * 10 + g_ascii_digit_value (*uri);
1602           uri++;
1603         }
1604       }
1605       uri = eoa;
1606     }
1607     if (uri != NULL && uri[0] != '\0') {
1608       /* get path */
1609       size_t len;
1610       len = strcspn (uri, "?#");
1611       if (uri[len] == '\0') {
1612         uri_obj->path = _gst_uri_string_to_list (uri, "/", FALSE, TRUE);
1613         uri = NULL;
1614       } else {
1615         if (len > 0) {
1616           gchar *path_str = g_strndup (uri, len);
1617           uri_obj->path = _gst_uri_string_to_list (path_str, "/", FALSE, TRUE);
1618           g_free (path_str);
1619         }
1620         uri += len;
1621       }
1622     }
1623     if (uri != NULL && uri[0] == '?') {
1624       /* get query */
1625       gchar *eoq;
1626       eoq = strchr (++uri, '#');
1627       if (eoq == NULL) {
1628         uri_obj->query = _gst_uri_string_to_table (uri, "&", "=", TRUE, TRUE);
1629         uri = NULL;
1630       } else {
1631         if (eoq != uri) {
1632           gchar *query_str = g_strndup (uri, eoq - uri);
1633           uri_obj->query = _gst_uri_string_to_table (query_str, "&", "=", TRUE,
1634               TRUE);
1635           g_free (query_str);
1636         }
1637         uri = eoq;
1638       }
1639     }
1640     if (uri != NULL && uri[0] == '#') {
1641       uri_obj->fragment = g_uri_unescape_string (uri + 1, NULL);
1642     }
1643   }
1644
1645   return uri_obj;
1646 }
1647
1648 /**
1649  * gst_uri_from_string_with_base:
1650  * @base: (transfer none)(nullable): The base URI to join the new URI with.
1651  * @uri: The URI string to parse.
1652  *
1653  * Like gst_uri_from_string() but also joins with a base URI.
1654  *
1655  * Returns: (transfer full): A new #GstUri object.
1656  *
1657  * Since: 1.6
1658  */
1659 GstUri *
1660 gst_uri_from_string_with_base (GstUri * base, const gchar * uri)
1661 {
1662   GstUri *new_rel_uri;
1663   GstUri *new_uri;
1664
1665   g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL);
1666
1667   new_rel_uri = gst_uri_from_string (uri);
1668   new_uri = gst_uri_join (base, new_rel_uri);
1669   gst_uri_unref (new_rel_uri);
1670
1671   return new_uri;
1672 }
1673
1674 /**
1675  * gst_uri_equal:
1676  * @first: First #GstUri to compare.
1677  * @second: Second #GstUri to compare.
1678  *
1679  * Compares two #GstUri objects to see if they represent the same normalized
1680  * URI.
1681  *
1682  * Returns: %TRUE if the normalized versions of the two URI's would be equal.
1683  *
1684  * Since: 1.6
1685  */
1686 gboolean
1687 gst_uri_equal (const GstUri * first, const GstUri * second)
1688 {
1689   gchar *first_norm = NULL, *second_norm = NULL;
1690   GList *first_norm_list = NULL, *second_norm_list = NULL;
1691   const gchar *first_cmp, *second_cmp;
1692   GHashTableIter table_iter;
1693   gpointer key, value;
1694   int result;
1695
1696   g_return_val_if_fail ((first == NULL || GST_IS_URI (first)) &&
1697       (second == NULL || GST_IS_URI (second)), FALSE);
1698
1699   if (first == second)
1700     return TRUE;
1701
1702   if (first == NULL || second == NULL)
1703     return FALSE;
1704
1705   if (first->port != second->port)
1706     return FALSE;
1707
1708 /* work out a version of field value (normalized or not) to compare.
1709  * first_cmp, second_cmp will be the values to compare later.
1710  * first_norm, second_norm will be non-NULL if normalized versions are used,
1711  *  and need to be freed later.
1712  */
1713 #define GST_URI_NORMALIZED_FIELD(pos, field, norm_fn, flags) \
1714   pos##_cmp = pos->field; \
1715   if (_gst_uri_first_non_normalized_char ((gchar*)pos##_cmp, flags) != NULL) { \
1716     pos##_norm = g_strdup (pos##_cmp); \
1717     norm_fn (pos##_norm); \
1718     pos##_cmp = pos##_norm; \
1719   }
1720
1721 /* compare two string values, normalizing if needed */
1722 #define GST_URI_NORMALIZED_CMP_STR(field, norm_fn, flags) \
1723   GST_URI_NORMALIZED_FIELD (first, field, norm_fn, flags) \
1724   GST_URI_NORMALIZED_FIELD (second, field, norm_fn, flags) \
1725   result = g_strcmp0 (first_cmp, second_cmp); \
1726   g_free (first_norm); \
1727   first_norm = NULL; \
1728   g_free (second_norm); \
1729   second_norm = NULL; \
1730   if (result != 0) return FALSE
1731
1732 /* compare two string values */
1733 #define GST_URI_CMP_STR(field) \
1734   if (g_strcmp0 (first->field, second->field) != 0) return FALSE
1735
1736 /* compare two GLists, normalize lists if needed before comparison */
1737 #define GST_URI_NORMALIZED_CMP_LIST(field, norm_fn, copy_fn, cmp_fn, free_fn) \
1738   first_norm_list = g_list_copy_deep (first->field, (GCopyFunc) copy_fn, NULL); \
1739   norm_fn (&first_norm_list); \
1740   second_norm_list = g_list_copy_deep (second->field, (GCopyFunc) copy_fn, NULL); \
1741   norm_fn (&second_norm_list); \
1742   result = _gst_uri_compare_lists (first_norm_list, second_norm_list, (GCompareFunc) cmp_fn); \
1743   g_list_free_full (first_norm_list, free_fn); \
1744   g_list_free_full (second_norm_list, free_fn); \
1745   if (result != 0) return FALSE
1746
1747   GST_URI_CMP_STR (userinfo);
1748
1749   GST_URI_CMP_STR (fragment);
1750
1751   GST_URI_NORMALIZED_CMP_STR (scheme, _gst_uri_normalize_scheme,
1752       _GST_URI_NORMALIZE_LOWERCASE);
1753
1754   GST_URI_NORMALIZED_CMP_STR (host, _gst_uri_normalize_hostname,
1755       _GST_URI_NORMALIZE_LOWERCASE);
1756
1757   GST_URI_NORMALIZED_CMP_LIST (path, _gst_uri_normalize_path, g_strdup,
1758       g_strcmp0, g_free);
1759
1760   if (first->query == NULL && second->query != NULL)
1761     return FALSE;
1762   if (first->query != NULL && second->query == NULL)
1763     return FALSE;
1764   if (first->query != NULL) {
1765     if (g_hash_table_size (first->query) != g_hash_table_size (second->query))
1766       return FALSE;
1767
1768     g_hash_table_iter_init (&table_iter, first->query);
1769     while (g_hash_table_iter_next (&table_iter, &key, &value)) {
1770       if (!g_hash_table_contains (second->query, key))
1771         return FALSE;
1772       result = g_strcmp0 (g_hash_table_lookup (second->query, key), value);
1773       if (result != 0)
1774         return FALSE;
1775     }
1776   }
1777 #undef GST_URI_NORMALIZED_CMP_STR
1778 #undef GST_URI_CMP_STR
1779 #undef GST_URI_NORMALIZED_CMP_LIST
1780 #undef GST_URI_NORMALIZED_FIELD
1781
1782   return TRUE;
1783 }
1784
1785 /**
1786  * gst_uri_join:
1787  * @base_uri: (transfer none)(nullable): The base URI to join another to.
1788  * @ref_uri: (transfer none)(nullable): The reference URI to join onto the
1789  *                                        base URI.
1790  *
1791  * Join a reference URI onto a base URI using the method from RFC 3986.
1792  * If either URI is %NULL then the other URI will be returned with the ref count
1793  * increased.
1794  *
1795  * Returns: (transfer full): A #GstUri which represents the base with the
1796  *                           reference URI joined on.
1797  *
1798  * Since: 1.6
1799  */
1800 GstUri *
1801 gst_uri_join (GstUri * base_uri, GstUri * ref_uri)
1802 {
1803   const gchar *r_scheme;
1804   GstUri *t;
1805
1806   g_return_val_if_fail ((base_uri == NULL || GST_IS_URI (base_uri)) &&
1807       (ref_uri == NULL || GST_IS_URI (ref_uri)), NULL);
1808
1809   if (base_uri == NULL && ref_uri == NULL)
1810     return NULL;
1811   if (base_uri == NULL) {
1812     g_return_val_if_fail (GST_IS_URI (ref_uri), NULL);
1813     return gst_uri_ref (ref_uri);
1814   }
1815   if (ref_uri == NULL) {
1816     g_return_val_if_fail (GST_IS_URI (base_uri), NULL);
1817     return gst_uri_ref (base_uri);
1818   }
1819
1820   g_return_val_if_fail (GST_IS_URI (base_uri) && GST_IS_URI (ref_uri), NULL);
1821
1822   t = _gst_uri_new ();
1823
1824   if (t == NULL)
1825     return t;
1826
1827   /* process according to RFC3986 */
1828   r_scheme = ref_uri->scheme;
1829   if (r_scheme != NULL && g_strcmp0 (base_uri->scheme, r_scheme) == 0) {
1830     r_scheme = NULL;
1831   }
1832   if (r_scheme != NULL) {
1833     t->scheme = g_strdup (r_scheme);
1834     t->userinfo = g_strdup (ref_uri->userinfo);
1835     t->host = g_strdup (ref_uri->host);
1836     t->port = ref_uri->port;
1837     t->path = _remove_dot_segments (ref_uri->path);
1838     t->query = _gst_uri_copy_query_table (ref_uri->query);
1839   } else {
1840     if (ref_uri->host != NULL) {
1841       t->userinfo = g_strdup (ref_uri->userinfo);
1842       t->host = g_strdup (ref_uri->host);
1843       t->port = ref_uri->port;
1844       t->path = _remove_dot_segments (ref_uri->path);
1845       t->query = _gst_uri_copy_query_table (ref_uri->query);
1846     } else {
1847       if (ref_uri->path == NULL) {
1848         t->path = g_list_copy_deep (base_uri->path, (GCopyFunc) g_strdup, NULL);
1849         if (ref_uri->query != NULL)
1850           t->query = _gst_uri_copy_query_table (ref_uri->query);
1851         else
1852           t->query = _gst_uri_copy_query_table (base_uri->query);
1853       } else {
1854         if (ref_uri->path->data == NULL)
1855           t->path = _remove_dot_segments (ref_uri->path);
1856         else {
1857           GList *mrgd = _merge (base_uri->path, ref_uri->path);
1858           t->path = _remove_dot_segments (mrgd);
1859           g_list_free_full (mrgd, g_free);
1860         }
1861         t->query = _gst_uri_copy_query_table (ref_uri->query);
1862       }
1863       t->userinfo = g_strdup (base_uri->userinfo);
1864       t->host = g_strdup (base_uri->host);
1865       t->port = base_uri->port;
1866     }
1867     t->scheme = g_strdup (base_uri->scheme);
1868   }
1869   t->fragment = g_strdup (ref_uri->fragment);
1870
1871   return t;
1872 }
1873
1874 /**
1875  * gst_uri_join_strings:
1876  * @base_uri: The percent-encoded base URI.
1877  * @ref_uri: The percent-encoded reference URI to join to the @base_uri.
1878  *
1879  * This is a convenience function to join two URI strings and return the result.
1880  * The returned string should be g_free()'d after use.
1881  *
1882  * Returns: (transfer full): A string representing the percent-encoded join of
1883  *          the two URIs.
1884  *
1885  * Since: 1.6
1886  */
1887 gchar *
1888 gst_uri_join_strings (const gchar * base_uri, const gchar * ref_uri)
1889 {
1890   GstUri *base, *result;
1891   gchar *result_uri;
1892
1893   base = gst_uri_from_string (base_uri);
1894   result = gst_uri_from_string_with_base (base, ref_uri);
1895   result_uri = gst_uri_to_string (result);
1896   gst_uri_unref (base);
1897   gst_uri_unref (result);
1898
1899   return result_uri;
1900 }
1901
1902 /**
1903  * gst_uri_is_writable:
1904  * @uri: The #GstUri object to test.
1905  *
1906  * Check if it is safe to write to this #GstUri.
1907  *
1908  * Check if the refcount of @uri is exactly 1, meaning that no other
1909  * reference exists to the #GstUri and that the #GstUri is therefore writable.
1910  *
1911  * Modification of a #GstUri should only be done after verifying that it is
1912  * writable.
1913  *
1914  * Returns: %TRUE if it is safe to write to the object.
1915  *
1916  * Since: 1.6
1917  */
1918 gboolean
1919 gst_uri_is_writable (const GstUri * uri)
1920 {
1921   g_return_val_if_fail (GST_IS_URI (uri), FALSE);
1922   return gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (uri));
1923 }
1924
1925 /**
1926  * gst_uri_make_writable:
1927  * @uri: (transfer full): The #GstUri object to make writable.
1928  *
1929  * Make the #GstUri writable.
1930  *
1931  * Checks if @uri is writable, and if so the original object is returned. If
1932  * not, then a writable copy is made and returned. This gives away the
1933  * reference to @uri and returns a reference to the new #GstUri.
1934  * If @uri is %NULL then %NULL is returned.
1935  *
1936  * Returns: (transfer full): A writable version of @uri.
1937  *
1938  * Since: 1.6
1939  */
1940 GstUri *
1941 gst_uri_make_writable (GstUri * uri)
1942 {
1943   g_return_val_if_fail (GST_IS_URI (uri), NULL);
1944   return
1945       GST_URI_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (uri)));
1946 }
1947
1948 /**
1949  * gst_uri_to_string:
1950  * @uri: This #GstUri to convert to a string.
1951  *
1952  * Convert the URI to a string.
1953  *
1954  * Returns the URI as held in this object as a #gchar* nul-terminated string.
1955  * The caller should g_free() the string once they are finished with it.
1956  * The string is put together as described in RFC 3986.
1957  *
1958  * Returns: (transfer full): The string version of the URI.
1959  *
1960  * Since: 1.6
1961  */
1962 gchar *
1963 gst_uri_to_string (const GstUri * uri)
1964 {
1965   GString *uri_str;
1966   gchar *escaped;
1967
1968   g_return_val_if_fail (GST_IS_URI (uri), NULL);
1969
1970   uri_str = g_string_new (NULL);
1971
1972   if (uri->scheme != NULL)
1973     g_string_append_printf (uri_str, "%s:", uri->scheme);
1974
1975   if (uri->userinfo != NULL || uri->host != NULL ||
1976       uri->port != GST_URI_NO_PORT)
1977     g_string_append (uri_str, "//");
1978
1979   if (uri->userinfo != NULL) {
1980     escaped = _gst_uri_escape_userinfo (uri->userinfo);
1981     g_string_append_printf (uri_str, "%s@", escaped);
1982     g_free (escaped);
1983   }
1984
1985   if (uri->host != NULL) {
1986     if (strchr (uri->host, ':') != NULL) {
1987       escaped = _gst_uri_escape_host_colon (uri->host);
1988       g_string_append_printf (uri_str, "[%s]", escaped);
1989       g_free (escaped);
1990     } else {
1991       escaped = _gst_uri_escape_host (uri->host);
1992       g_string_append (uri_str, escaped);
1993       g_free (escaped);
1994     }
1995   }
1996
1997   if (uri->port != GST_URI_NO_PORT)
1998     g_string_append_printf (uri_str, ":%u", uri->port);
1999
2000   if (uri->path != NULL) {
2001     escaped = gst_uri_get_path_string (uri);
2002     g_string_append (uri_str, escaped);
2003     g_free (escaped);
2004   }
2005
2006   if (uri->query) {
2007     g_string_append (uri_str, "?");
2008     escaped = gst_uri_get_query_string (uri);
2009     g_string_append (uri_str, escaped);
2010     g_free (escaped);
2011   }
2012
2013   if (uri->fragment != NULL) {
2014     escaped = _gst_uri_escape_fragment (uri->fragment);
2015     g_string_append_printf (uri_str, "#%s", escaped);
2016     g_free (escaped);
2017   }
2018
2019   return g_string_free (uri_str, FALSE);
2020 }
2021
2022 /**
2023  * gst_uri_is_normalized:
2024  * @uri: The #GstUri to test to see if it is normalized.
2025  *
2026  * Tests the @uri to see if it is normalized. A %NULL @uri is considered to be
2027  * normalized.
2028  *
2029  * Returns: TRUE if the URI is normalized or is %NULL.
2030  *
2031  * Since: 1.6
2032  */
2033 gboolean
2034 gst_uri_is_normalized (const GstUri * uri)
2035 {
2036   GList *new_path;
2037   gboolean ret;
2038
2039   if (uri == NULL)
2040     return TRUE;
2041
2042   g_return_val_if_fail (GST_IS_URI (uri), FALSE);
2043
2044   /* check for non-normalized characters in uri parts */
2045   if (_gst_uri_first_non_normalized_char (uri->scheme,
2046           _GST_URI_NORMALIZE_LOWERCASE) != NULL ||
2047       /*_gst_uri_first_non_normalized_char (uri->userinfo,
2048           _GST_URI_NORMALIZE_PERCENTAGES) != NULL || */
2049       _gst_uri_first_non_normalized_char (uri->host,
2050           _GST_URI_NORMALIZE_LOWERCASE /*| _GST_URI_NORMALIZE_PERCENTAGES */ )
2051       != NULL
2052       /*|| _gst_uri_first_non_normalized_char (uri->path,
2053          _GST_URI_NORMALIZE_PERCENTAGES) != NULL
2054          || _gst_uri_first_non_normalized_char (uri->query,
2055          _GST_URI_NORMALIZE_PERCENTAGES) != NULL
2056          || _gst_uri_first_non_normalized_char (uri->fragment,
2057          _GST_URI_NORMALIZE_PERCENTAGES) != NULL */ )
2058     return FALSE;
2059
2060   /* also check path has had dot segments removed */
2061   new_path = _remove_dot_segments (uri->path);
2062   ret =
2063       (_gst_uri_compare_lists (new_path, uri->path,
2064           (GCompareFunc) g_strcmp0) == 0);
2065   g_list_free_full (new_path, g_free);
2066   return ret;
2067 }
2068
2069 /**
2070  * gst_uri_normalize:
2071  * @uri: (transfer none): The #GstUri to normalize.
2072  *
2073  * Normalization will remove extra path segments ("." and "..") from the URI. It
2074  * will also convert the scheme and host name to lower case and any
2075  * percent-encoded values to uppercase.
2076  *
2077  * The #GstUri object must be writable. Check with gst_uri_is_writable() or use
2078  * gst_uri_make_writable() first.
2079  *
2080  * Returns: TRUE if the URI was modified.
2081  *
2082  * Since: 1.6
2083  */
2084 gboolean
2085 gst_uri_normalize (GstUri * uri)
2086 {
2087   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2088
2089   return _gst_uri_normalize_scheme (uri->scheme) |
2090       _gst_uri_normalize_userinfo (uri->userinfo) |
2091       _gst_uri_normalize_hostname (uri->host) |
2092       _gst_uri_normalize_path (&uri->path) |
2093       _gst_uri_normalize_query (uri->query) |
2094       _gst_uri_normalize_fragment (uri->fragment);
2095 }
2096
2097 /**
2098  * gst_uri_get_scheme:
2099  * @uri: (nullable): This #GstUri object.
2100  *
2101  * Get the scheme name from the URI or %NULL if it doesn't exist.
2102  * If @uri is %NULL then returns %NULL.
2103  *
2104  * Returns: The scheme from the #GstUri object or %NULL.
2105  */
2106 const gchar *
2107 gst_uri_get_scheme (const GstUri * uri)
2108 {
2109   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2110   return (uri ? uri->scheme : NULL);
2111 }
2112
2113 /**
2114  * gst_uri_set_scheme:
2115  * @uri: (transfer none)(nullable): The #GstUri to modify.
2116  * @scheme: The new scheme to set or %NULL to unset the scheme.
2117  *
2118  * Set or unset the scheme for the URI.
2119  *
2120  * Returns: %TRUE if the scheme was set/unset successfully.
2121  *
2122  * Since: 1.6
2123  */
2124 gboolean
2125 gst_uri_set_scheme (GstUri * uri, const gchar * scheme)
2126 {
2127   if (!uri)
2128     return scheme == NULL;
2129   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2130
2131   g_free (uri->scheme);
2132   uri->scheme = g_strdup (scheme);
2133
2134   return TRUE;
2135 }
2136
2137 /**
2138  * gst_uri_get_userinfo:
2139  * @uri: (nullable): This #GstUri object.
2140  *
2141  * Get the userinfo (usually in the form "username:password") from the URI
2142  * or %NULL if it doesn't exist. If @uri is %NULL then returns %NULL.
2143  *
2144  * Returns: The userinfo from the #GstUri object or %NULL.
2145  *
2146  * Since: 1.6
2147  */
2148 const gchar *
2149 gst_uri_get_userinfo (const GstUri * uri)
2150 {
2151   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2152   return (uri ? uri->userinfo : NULL);
2153 }
2154
2155 /**
2156  * gst_uri_set_userinfo:
2157  * @uri: (transfer none)(nullable): The #GstUri to modify.
2158  * @userinfo: The new user-information string to set or %NULL to unset.
2159  *
2160  * Set or unset the user information for the URI.
2161  *
2162  * Returns: %TRUE if the user information was set/unset successfully.
2163  *
2164  * Since: 1.6
2165  */
2166 gboolean
2167 gst_uri_set_userinfo (GstUri * uri, const gchar * userinfo)
2168 {
2169   if (!uri)
2170     return userinfo == NULL;
2171   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2172
2173   g_free (uri->userinfo);
2174   uri->userinfo = g_strdup (userinfo);
2175
2176   return TRUE;
2177 }
2178
2179 /**
2180  * gst_uri_get_host:
2181  * @uri: (nullable): This #GstUri object.
2182  *
2183  * Get the host name from the URI or %NULL if it doesn't exist.
2184  * If @uri is %NULL then returns %NULL.
2185  *
2186  * Returns: The host name from the #GstUri object or %NULL.
2187  *
2188  * Since: 1.6
2189  */
2190 const gchar *
2191 gst_uri_get_host (const GstUri * uri)
2192 {
2193   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2194   return (uri ? uri->host : NULL);
2195 }
2196
2197 /**
2198  * gst_uri_set_host:
2199  * @uri: (transfer none)(nullable): The #GstUri to modify.
2200  * @host: The new host string to set or %NULL to unset.
2201  *
2202  * Set or unset the host for the URI.
2203  *
2204  * Returns: %TRUE if the host was set/unset successfully.
2205  *
2206  * Since: 1.6
2207  */
2208 gboolean
2209 gst_uri_set_host (GstUri * uri, const gchar * host)
2210 {
2211   if (!uri)
2212     return host == NULL;
2213   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2214
2215   g_free (uri->host);
2216   uri->host = g_strdup (host);
2217
2218   return TRUE;
2219 }
2220
2221 /**
2222  * gst_uri_get_port:
2223  * @uri: (nullable): This #GstUri object.
2224  *
2225  * Get the port number from the URI or %GST_URI_NO_PORT if it doesn't exist.
2226  * If @uri is %NULL then returns %GST_URI_NO_PORT.
2227  *
2228  * Returns: The port number from the #GstUri object or %GST_URI_NO_PORT.
2229  *
2230  * Since: 1.6
2231  */
2232 guint
2233 gst_uri_get_port (const GstUri * uri)
2234 {
2235   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), GST_URI_NO_PORT);
2236   return (uri ? uri->port : GST_URI_NO_PORT);
2237 }
2238
2239 /**
2240  * gst_uri_set_port:
2241  * @uri: (transfer none)(nullable): The #GstUri to modify.
2242  * @port: The new port number to set or %GST_URI_NO_PORT to unset.
2243  *
2244  * Set or unset the port number for the URI.
2245  *
2246  * Returns: %TRUE if the port number was set/unset successfully.
2247  *
2248  * Since: 1.6
2249  */
2250 gboolean
2251 gst_uri_set_port (GstUri * uri, guint port)
2252 {
2253   if (!uri)
2254     return port == GST_URI_NO_PORT;
2255   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2256
2257   uri->port = port;
2258
2259   return TRUE;
2260 }
2261
2262 /**
2263  * gst_uri_get_path:
2264  * @uri: The #GstUri to get the path from.
2265  *
2266  * Extract the path string from the URI object.
2267  *
2268  * Returns: (transfer full): The path from the URI. Once finished with the
2269  *                           string should be g_free()'d.
2270  *
2271  * Since: 1.6
2272  */
2273 gchar *
2274 gst_uri_get_path (const GstUri * uri)
2275 {
2276   GList *path_segment;
2277   const gchar *sep = "";
2278   GString *ret;
2279
2280   if (!uri)
2281     return NULL;
2282   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2283   if (!uri->path)
2284     return NULL;
2285
2286   ret = g_string_new (NULL);
2287
2288   for (path_segment = uri->path; path_segment;
2289       path_segment = path_segment->next) {
2290     g_string_append (ret, sep);
2291     if (path_segment->data) {
2292       g_string_append (ret, path_segment->data);
2293     }
2294     sep = "/";
2295   }
2296
2297   return g_string_free (ret, FALSE);
2298 }
2299
2300 /**
2301  * gst_uri_set_path:
2302  * @uri: (transfer none)(nullable): The #GstUri to modify.
2303  * @path: The new path to set with path segments separated by '/', or use %NULL
2304  *        to unset the path.
2305  *
2306  * Sets or unsets the path in the URI.
2307  *
2308  * Returns: %TRUE if the path was set successfully.
2309  *
2310  * Since: 1.6
2311  */
2312 gboolean
2313 gst_uri_set_path (GstUri * uri, const gchar * path)
2314 {
2315   if (!uri)
2316     return path == NULL;
2317   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2318
2319   g_list_free_full (uri->path, g_free);
2320   uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE);
2321
2322   return TRUE;
2323 }
2324
2325 /**
2326  * gst_uri_get_path_string:
2327  * @uri: The #GstUri to get the path from.
2328  *
2329  * Extract the path string from the URI object as a percent encoded URI path.
2330  *
2331  * Returns: (transfer full): The path from the URI. Once finished with the
2332  *                           string should be g_free()'d.
2333  *
2334  * Since: 1.6
2335  */
2336 gchar *
2337 gst_uri_get_path_string (const GstUri * uri)
2338 {
2339   GList *path_segment;
2340   const gchar *sep = "";
2341   GString *ret;
2342   gchar *escaped;
2343
2344   if (!uri)
2345     return NULL;
2346   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2347   if (!uri->path)
2348     return NULL;
2349
2350   ret = g_string_new (NULL);
2351
2352   for (path_segment = uri->path; path_segment;
2353       path_segment = path_segment->next) {
2354     g_string_append (ret, sep);
2355     if (path_segment->data) {
2356       escaped = _gst_uri_escape_path_segment (path_segment->data);
2357       g_string_append (ret, escaped);
2358       g_free (escaped);
2359     }
2360     sep = "/";
2361   }
2362
2363   return g_string_free (ret, FALSE);
2364 }
2365
2366 /**
2367  * gst_uri_set_path_string:
2368  * @uri: (transfer none)(nullable): The #GstUri to modify.
2369  * @path: The new percent encoded path to set with path segments separated by
2370  * '/', or use %NULL to unset the path.
2371  *
2372  * Sets or unsets the path in the URI.
2373  *
2374  * Returns: %TRUE if the path was set successfully.
2375  *
2376  * Since: 1.6
2377  */
2378 gboolean
2379 gst_uri_set_path_string (GstUri * uri, const gchar * path)
2380 {
2381   if (!uri)
2382     return path == NULL;
2383   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2384
2385   g_list_free_full (uri->path, g_free);
2386   uri->path = _gst_uri_string_to_list (path, "/", FALSE, TRUE);
2387   return TRUE;
2388 }
2389
2390 /**
2391  * gst_uri_get_path_segments:
2392  * @uri: (nullable): The #GstUri to get the path from.
2393  *
2394  * Get a list of path segments from the URI.
2395  *
2396  * Returns: (transfer full)(element-type gchar*): A #GList of path segment
2397  *          strings or %NULL if no path segments are available. Free the list
2398  *          when no longer needed with g_list_free_full(list, g_free).
2399  *
2400  * Since: 1.6
2401  */
2402 GList *
2403 gst_uri_get_path_segments (const GstUri * uri)
2404 {
2405   GList *ret = NULL;
2406
2407   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2408
2409   if (uri) {
2410     ret = g_list_copy_deep (uri->path, (GCopyFunc) g_strdup, NULL);
2411   }
2412
2413   return ret;
2414 }
2415
2416 /**
2417  * gst_uri_set_path_segments:
2418  * @uri: (transfer none)(nullable): The #GstUri to modify.
2419  * @path_segments: (transfer full)(nullable)(element-type gchar*): The new
2420  *                 path list to set.
2421  *
2422  * Replace the path segments list in the URI.
2423  *
2424  * Returns: %TRUE if the path segments were set successfully.
2425  *
2426  * Since: 1.6
2427  */
2428 gboolean
2429 gst_uri_set_path_segments (GstUri * uri, GList * path_segments)
2430 {
2431   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), FALSE);
2432
2433   if (!uri) {
2434     if (path_segments)
2435       g_list_free_full (path_segments, g_free);
2436     return path_segments == NULL;
2437   }
2438
2439   g_return_val_if_fail (gst_uri_is_writable (uri), FALSE);
2440
2441   g_list_free_full (uri->path, g_free);
2442   uri->path = path_segments;
2443   return TRUE;
2444 }
2445
2446 /**
2447  * gst_uri_append_path:
2448  * @uri: (transfer none)(nullable): The #GstUri to modify.
2449  * @relative_path: Relative path to append to the end of the current path.
2450  *
2451  * Append a path onto the end of the path in the URI. The path is not
2452  * normalized, call #gst_uri_normalize() to normalize the path.
2453  *
2454  * Returns: %TRUE if the path was appended successfully.
2455  *
2456  * Since: 1.6
2457  */
2458 gboolean
2459 gst_uri_append_path (GstUri * uri, const gchar * relative_path)
2460 {
2461   GList *rel_path_list;
2462
2463   if (!uri)
2464     return relative_path == NULL;
2465   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2466   if (!relative_path)
2467     return TRUE;
2468
2469   if (uri->path) {
2470     GList *last_elem = g_list_last (uri->path);
2471     if (last_elem->data == NULL) {
2472       uri->path = g_list_delete_link (uri->path, last_elem);
2473     }
2474   }
2475   rel_path_list = _gst_uri_string_to_list (relative_path, "/", FALSE, FALSE);
2476   /* if path was absolute, make it relative by removing initial NULL element */
2477   if (rel_path_list && rel_path_list->data == NULL) {
2478     rel_path_list = g_list_delete_link (rel_path_list, rel_path_list);
2479   }
2480   uri->path = g_list_concat (uri->path, rel_path_list);
2481   return TRUE;
2482 }
2483
2484 /**
2485  * gst_uri_append_path_segment:
2486  * @uri: (transfer none)(nullable): The #GstUri to modify.
2487  * @path_segment: The path segment string to append to the URI path.
2488  *
2489  * Append a single path segment onto the end of the URI path.
2490  *
2491  * Returns: %TRUE if the path was appended successfully.
2492  *
2493  * Since: 1.6
2494  */
2495 gboolean
2496 gst_uri_append_path_segment (GstUri * uri, const gchar * path_segment)
2497 {
2498   if (!uri)
2499     return path_segment == NULL;
2500   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2501   if (!path_segment)
2502     return TRUE;
2503
2504   /* if base path ends in a directory (i.e. last element is NULL), remove it */
2505   if (uri->path && g_list_last (uri->path)->data == NULL) {
2506     uri->path = g_list_delete_link (uri->path, g_list_last (uri->path));
2507   }
2508   uri->path = g_list_append (uri->path, g_strdup (path_segment));
2509   return TRUE;
2510 }
2511
2512 /**
2513  * gst_uri_get_query_string:
2514  * @uri: (nullable): The #GstUri to get the query string from.
2515  *
2516  * Get a percent encoded URI query string from the @uri.
2517  *
2518  * Returns: (transfer full): A percent encoded query string. Use g_free() when
2519  *          no longer needed.
2520  *
2521  * Since: 1.6
2522  */
2523 gchar *
2524 gst_uri_get_query_string (const GstUri * uri)
2525 {
2526   GHashTableIter iter;
2527   gpointer key, value;
2528   const gchar *sep = "";
2529   gchar *escaped;
2530   GString *ret;
2531
2532   if (!uri)
2533     return NULL;
2534   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2535   if (!uri->query)
2536     return NULL;
2537
2538   ret = g_string_new (NULL);
2539   g_hash_table_iter_init (&iter, uri->query);
2540   while (g_hash_table_iter_next (&iter, &key, &value)) {
2541     g_string_append (ret, sep);
2542     escaped = _gst_uri_escape_http_query_element (key);
2543     g_string_append (ret, escaped);
2544     g_free (escaped);
2545     if (value) {
2546       escaped = _gst_uri_escape_http_query_element (value);
2547       g_string_append_printf (ret, "=%s", escaped);
2548       g_free (escaped);
2549     }
2550     sep = "&";
2551   }
2552
2553   return g_string_free (ret, FALSE);
2554 }
2555
2556 /**
2557  * gst_uri_set_query_string:
2558  * @uri: (transfer none)(nullable): The #GstUri to modify.
2559  * @query: The new percent encoded query string to use to populate the query
2560  *        table, or use %NULL to unset the query table.
2561  *
2562  * Sets or unsets the query table in the URI.
2563  *
2564  * Returns: %TRUE if the query table was set successfully.
2565  *
2566  * Since: 1.6
2567  */
2568 gboolean
2569 gst_uri_set_query_string (GstUri * uri, const gchar * query)
2570 {
2571   if (!uri)
2572     return query == NULL;
2573
2574   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2575
2576   if (uri->query)
2577     g_hash_table_unref (uri->query);
2578   uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, TRUE);
2579
2580   return TRUE;
2581 }
2582
2583 /**
2584  * gst_uri_get_query_table:
2585  * @uri: (nullable): The #GstUri to get the query table from.
2586  *
2587  * Get the query table from the URI. Keys and values in the table are freed
2588  * with g_free when they are deleted. A value may be %NULL to indicate that
2589  * the key should appear in the query string in the URI, but does not have a
2590  * value. Free the returned #GHashTable with #g_hash_table_unref() when it is
2591  * no longer required. Modifying this hash table will modify the query in the
2592  * URI.
2593  *
2594  * Returns: (transfer full)(element-type gchar* gchar*): The query hash table
2595  *          from the URI.
2596  *
2597  * Since: 1.6
2598  */
2599 GHashTable *
2600 gst_uri_get_query_table (const GstUri * uri)
2601 {
2602   if (!uri)
2603     return NULL;
2604   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2605   if (!uri->query)
2606     return NULL;
2607
2608   return g_hash_table_ref (uri->query);
2609 }
2610
2611 /**
2612  * gst_uri_set_query_table:
2613  * @uri: (transfer none)(nullable): The #GstUri to modify.
2614  * @query_table: (transfer none)(nullable)(element-type gchar* gchar*): The new
2615  *               query table to use.
2616  *
2617  * Set the query table to use in the URI. The old table is unreferenced and a
2618  * reference to the new one is used instead. A value if %NULL for @query_table
2619  * will remove the query string from the URI.
2620  *
2621  * Returns: %TRUE if the new table was sucessfully used for the query table.
2622  *
2623  * Since: 1.6
2624  */
2625 gboolean
2626 gst_uri_set_query_table (GstUri * uri, GHashTable * query_table)
2627 {
2628   GHashTable *old_table = NULL;
2629
2630   if (!uri)
2631     return query_table == NULL;
2632   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2633
2634   old_table = uri->query;
2635   if (query_table)
2636     uri->query = g_hash_table_ref (query_table);
2637   else
2638     uri->query = NULL;
2639   if (old_table)
2640     g_hash_table_unref (old_table);
2641
2642   return TRUE;
2643 }
2644
2645 /**
2646  * gst_uri_set_query_value:
2647  * @uri: (transfer none)(nullable): The #GstUri to modify.
2648  * @query_key: (transfer none): The key for the query entry.
2649  * @query_value: (transfer none)(nullable): The value for the key.
2650  *
2651  * This inserts or replaces a key in the query table. A @query_value of %NULL
2652  * indicates that the key has no associated value, but will still be present in
2653  * the query string.
2654  *
2655  * Returns: %TRUE if the query table was sucessfully updated.
2656  *
2657  * Since: 1.6
2658  */
2659 gboolean
2660 gst_uri_set_query_value (GstUri * uri, const gchar * query_key,
2661     const gchar * query_value)
2662 {
2663   if (!uri)
2664     return FALSE;
2665   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2666
2667   if (!uri->query) {
2668     uri->query = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
2669         g_free);
2670   }
2671   g_hash_table_insert (uri->query, g_strdup (query_key),
2672       g_strdup (query_value));
2673
2674   return TRUE;
2675 }
2676
2677 /**
2678  * gst_uri_remove_query_key:
2679  * @uri: (transfer none)(nullable): The #GstUri to modify.
2680  * @query_key: The key to remove.
2681  *
2682  * Remove an entry from the query table by key.
2683  *
2684  * Returns: %TRUE if the key existed in the table and was removed.
2685  *
2686  * Since: 1.6
2687  */
2688 gboolean
2689 gst_uri_remove_query_key (GstUri * uri, const gchar * query_key)
2690 {
2691   gboolean result;
2692
2693   if (!uri)
2694     return FALSE;
2695   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2696   if (!uri->query)
2697     return FALSE;
2698
2699   result = g_hash_table_remove (uri->query, query_key);
2700   /* if this was the last query entry, remove the query string completely */
2701   if (result && g_hash_table_size (uri->query) == 0) {
2702     g_hash_table_unref (uri->query);
2703     uri->query = NULL;
2704   }
2705   return result;
2706 }
2707
2708 /**
2709  * gst_uri_query_has_key:
2710  * @uri: (nullable): The #GstUri to examine.
2711  * @query_key: The key to lookup.
2712  *
2713  * Check if there is a query table entry for the @query_key key.
2714  *
2715  * Returns: %TRUE if @query_key exists in the URI query table.
2716  *
2717  * Since: 1.6
2718  */
2719 gboolean
2720 gst_uri_query_has_key (const GstUri * uri, const gchar * query_key)
2721 {
2722   if (!uri)
2723     return FALSE;
2724   g_return_val_if_fail (GST_IS_URI (uri), FALSE);
2725   if (!uri->query)
2726     return FALSE;
2727
2728   return g_hash_table_contains (uri->query, query_key);
2729 }
2730
2731 /**
2732  * gst_uri_get_query_value:
2733  * @uri: (nullable): The #GstUri to examine.
2734  * @query_key: The key to lookup.
2735  *
2736  * Get the value associated with the @query_key key. Will return %NULL if the
2737  * key has no value or if the key does not exist in the URI query table. Because
2738  * %NULL is returned for both missing keys and keys with no value, you should
2739  * use gst_uri_query_has_key() to determine if a key is present in the URI
2740  * query.
2741  *
2742  * Returns: The value for the given key, or %NULL if not found.
2743  *
2744  * Since: 1.6
2745  */
2746 const gchar *
2747 gst_uri_get_query_value (const GstUri * uri, const gchar * query_key)
2748 {
2749   if (!uri)
2750     return NULL;
2751   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2752   if (!uri->query)
2753     return NULL;
2754
2755   return g_hash_table_lookup (uri->query, query_key);
2756 }
2757
2758 /**
2759  * gst_uri_get_query_keys:
2760  * @uri: (nullable): The #GstUri to examine.
2761  *
2762  * Get a list of the query keys from the URI.
2763  *
2764  * Returns: (transfer container)(element-type gchar*): A list of keys from
2765  *          the URI query. Free the list with g_list_free().
2766  *
2767  * Since: 1.6
2768  */
2769 GList *
2770 gst_uri_get_query_keys (const GstUri * uri)
2771 {
2772   if (!uri)
2773     return NULL;
2774   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2775   if (!uri->query)
2776     return NULL;
2777
2778   return g_hash_table_get_keys (uri->query);
2779 }
2780
2781 /**
2782  * gst_uri_get_fragment:
2783  * @uri: (nullable): This #GstUri object.
2784  *
2785  * Get the fragment name from the URI or %NULL if it doesn't exist.
2786  * If @uri is %NULL then returns %NULL.
2787  *
2788  * Returns: The host name from the #GstUri object or %NULL.
2789  *
2790  * Since: 1.6
2791  */
2792 const gchar *
2793 gst_uri_get_fragment (const GstUri * uri)
2794 {
2795   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2796   return (uri ? uri->fragment : NULL);
2797 }
2798
2799 /**
2800  * gst_uri_set_fragment:
2801  * @uri: (transfer none)(nullable): The #GstUri to modify.
2802  * @fragment: (nullable): The fragment string to set.
2803  *
2804  * Sets the fragment string in the URI. Use a value of %NULL in @fragment to
2805  * unset the fragment string.
2806  *
2807  * Returns: %TRUE if the fragment was set/unset successfully.
2808  *
2809  * Since: 1.6
2810  */
2811 gboolean
2812 gst_uri_set_fragment (GstUri * uri, const gchar * fragment)
2813 {
2814   if (!uri)
2815     return fragment == NULL;
2816   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2817
2818   g_free (uri->fragment);
2819   uri->fragment = g_strdup (fragment);
2820   return TRUE;
2821 }