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