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