Automatic update of common submodule
[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   uri = GST_URI_CAST (g_slice_new0 (GstUri));
998
999   if (uri)
1000     gst_mini_object_init (GST_MINI_OBJECT_CAST (uri), 0, gst_uri_get_type (),
1001         (GstMiniObjectCopyFunction) _gst_uri_copy, NULL,
1002         (GstMiniObjectFreeFunction) _gst_uri_free);
1003
1004   return uri;
1005 }
1006
1007 static void
1008 _gst_uri_free (GstUri * uri)
1009 {
1010   g_return_if_fail (GST_IS_URI (uri));
1011
1012   g_free (uri->scheme);
1013   g_free (uri->userinfo);
1014   g_free (uri->host);
1015   g_list_free_full (uri->path, g_free);
1016   if (uri->query)
1017     g_hash_table_unref (uri->query);
1018   g_free (uri->fragment);
1019
1020   g_slice_free1 (sizeof (*uri), uri);
1021 }
1022
1023 static GHashTable *
1024 _gst_uri_copy_query_table (GHashTable * orig)
1025 {
1026   GHashTable *new = NULL;
1027
1028   if (orig != NULL) {
1029     GHashTableIter iter;
1030     gpointer key, value;
1031     new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1032     g_hash_table_iter_init (&iter, orig);
1033     while (g_hash_table_iter_next (&iter, &key, &value)) {
1034       g_hash_table_insert (new, g_strdup (key), g_strdup (value));
1035     }
1036   }
1037
1038   return new;
1039 }
1040
1041 static GstUri *
1042 _gst_uri_copy (const GstUri * orig_uri)
1043 {
1044   GstUri *new_uri;
1045
1046   g_return_val_if_fail (GST_IS_URI (orig_uri), NULL);
1047
1048   new_uri = _gst_uri_new ();
1049
1050   if (new_uri) {
1051     new_uri->scheme = g_strdup (orig_uri->scheme);
1052     new_uri->userinfo = g_strdup (orig_uri->userinfo);
1053     new_uri->host = g_strdup (orig_uri->host);
1054     new_uri->port = orig_uri->port;
1055     new_uri->path = g_list_copy_deep (orig_uri->path, (GCopyFunc) g_strdup,
1056         NULL);
1057     new_uri->query = _gst_uri_copy_query_table (orig_uri->query);
1058     new_uri->fragment = g_strdup (orig_uri->fragment);
1059   }
1060
1061   return new_uri;
1062 }
1063
1064 /*
1065  * _gst_uri_compare_lists:
1066  *
1067  * Compare two lists for equality. This compares the two lists, item for item,
1068  * comparing items in the same position in the two lists. If @first is
1069  * considered less than @second the result will be negative. If @first is
1070  * considered to be more than @second then the result will be positive. If the
1071  * lists are considered to be equal then the result will be 0. If two lists
1072  * have the same items, but one list is shorter than the other, then the
1073  * shorter list is considered to be less than the longer list.
1074  */
1075 static gint
1076 _gst_uri_compare_lists (GList * first, GList * second, GCompareFunc cmp_fn)
1077 {
1078   GList *itr1, *itr2;
1079   gint result;
1080
1081   for (itr1 = first, itr2 = second;
1082       itr1 != NULL || itr2 != NULL; itr1 = itr1->next, itr2 = itr2->next) {
1083     if (itr1 == NULL)
1084       return -1;
1085     if (itr2 == NULL)
1086       return 1;
1087     result = cmp_fn (itr1->data, itr2->data);
1088     if (result != 0)
1089       return result;
1090   }
1091   return 0;
1092 }
1093
1094 typedef enum
1095 {
1096   _GST_URI_NORMALIZE_LOWERCASE = 1,
1097   _GST_URI_NORMALIZE_UPPERCASE = 2
1098 } _GstUriNormalizations;
1099
1100 /*
1101  * Find the first character that hasn't been normalized according to the @flags.
1102  */
1103 static gchar *
1104 _gst_uri_first_non_normalized_char (gchar * str, guint flags)
1105 {
1106   gchar *pos;
1107
1108   if (str == NULL)
1109     return NULL;
1110
1111   for (pos = str; *pos; pos++) {
1112     if ((flags & _GST_URI_NORMALIZE_UPPERCASE) && g_ascii_islower (*pos))
1113       return pos;
1114     if ((flags & _GST_URI_NORMALIZE_LOWERCASE) && g_ascii_isupper (*pos))
1115       return pos;
1116   }
1117   return NULL;
1118 }
1119
1120 static gboolean
1121 _gst_uri_normalize_lowercase (gchar * str)
1122 {
1123   gchar *pos;
1124   gboolean ret = FALSE;
1125
1126   for (pos = _gst_uri_first_non_normalized_char (str,
1127           _GST_URI_NORMALIZE_LOWERCASE);
1128       pos != NULL;
1129       pos = _gst_uri_first_non_normalized_char (pos + 1,
1130           _GST_URI_NORMALIZE_LOWERCASE)) {
1131     *pos = g_ascii_tolower (*pos);
1132     ret = TRUE;
1133   }
1134
1135   return ret;
1136 }
1137
1138 #define _gst_uri_normalize_scheme _gst_uri_normalize_lowercase
1139 #define _gst_uri_normalize_hostname _gst_uri_normalize_lowercase
1140
1141 static gboolean
1142 _gst_uri_normalize_path (GList ** path)
1143 {
1144   GList *new_path;
1145
1146   new_path = _remove_dot_segments (*path);
1147   if (_gst_uri_compare_lists (new_path, *path, (GCompareFunc) g_strcmp0) != 0) {
1148     g_list_free_full (*path, g_free);
1149     *path = new_path;
1150     return TRUE;
1151   }
1152   g_list_free_full (new_path, g_free);
1153
1154   return FALSE;
1155 }
1156
1157 static gboolean
1158 _gst_uri_normalize_str_noop (gchar * str)
1159 {
1160   return FALSE;
1161 }
1162
1163 static gboolean
1164 _gst_uri_normalize_table_noop (GHashTable * table)
1165 {
1166   return FALSE;
1167 }
1168
1169 #define _gst_uri_normalize_userinfo _gst_uri_normalize_str_noop
1170 #define _gst_uri_normalize_query _gst_uri_normalize_table_noop
1171 #define _gst_uri_normalize_fragment _gst_uri_normalize_str_noop
1172
1173 /* RFC 3986 functions */
1174
1175 static GList *
1176 _merge (GList * base, GList * path)
1177 {
1178   GList *ret, *path_copy, *last;
1179
1180   path_copy = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL);
1181   /* if base is NULL make path absolute */
1182   if (base == NULL) {
1183     if (path_copy != NULL && path_copy->data != NULL) {
1184       path_copy = g_list_prepend (path_copy, NULL);
1185     }
1186     return path_copy;
1187   }
1188
1189   ret = g_list_copy_deep (base, (GCopyFunc) g_strdup, NULL);
1190   last = g_list_last (ret);
1191   ret = g_list_remove_link (ret, last);
1192   g_list_free_full (last, g_free);
1193   ret = g_list_concat (ret, path_copy);
1194
1195   return ret;
1196 }
1197
1198 static GList *
1199 _remove_dot_segments (GList * path)
1200 {
1201   GList *out, *elem, *next;
1202
1203   if (path == NULL)
1204     return NULL;
1205
1206   out = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL);
1207
1208   for (elem = out; elem; elem = next) {
1209     next = elem->next;
1210     if (elem->data == NULL && elem != out && next != NULL) {
1211       out = g_list_delete_link (out, elem);
1212     } else if (g_strcmp0 (elem->data, ".") == 0) {
1213       g_free (elem->data);
1214       out = g_list_delete_link (out, elem);
1215     } else if (g_strcmp0 (elem->data, "..") == 0) {
1216       GList *prev = g_list_previous (elem);
1217       if (prev && (prev != out || prev->data != NULL)) {
1218         g_free (prev->data);
1219         out = g_list_delete_link (out, prev);
1220       }
1221       g_free (elem->data);
1222       if (next != NULL) {
1223         out = g_list_delete_link (out, elem);
1224       } else {
1225         /* path ends in '/..' We need to keep the last '/' */
1226         elem->data = NULL;
1227       }
1228     }
1229   }
1230
1231   return out;
1232 }
1233
1234 static gchar *
1235 _gst_uri_escape_userinfo (const gchar * userinfo)
1236 {
1237   return g_uri_escape_string (userinfo,
1238       G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, FALSE);
1239 }
1240
1241 static gchar *
1242 _gst_uri_escape_host (const gchar * host)
1243 {
1244   return g_uri_escape_string (host,
1245       G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, FALSE);
1246 }
1247
1248 static gchar *
1249 _gst_uri_escape_host_colon (const gchar * host)
1250 {
1251   return g_uri_escape_string (host,
1252       G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS ":", FALSE);
1253 }
1254
1255 static gchar *
1256 _gst_uri_escape_path_segment (const gchar * segment)
1257 {
1258   return g_uri_escape_string (segment,
1259       G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT, FALSE);
1260 }
1261
1262 static gchar *
1263 _gst_uri_escape_http_query_element (const gchar * element)
1264 {
1265   gchar *ret, *c;
1266
1267   ret = g_uri_escape_string (element, "!$'()*,;:@/? ", FALSE);
1268   for (c = ret; *c; c++)
1269     if (*c == ' ')
1270       *c = '+';
1271   return ret;
1272 }
1273
1274 static gchar *
1275 _gst_uri_escape_fragment (const gchar * fragment)
1276 {
1277   return g_uri_escape_string (fragment,
1278       G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?", FALSE);
1279 }
1280
1281 static GList *
1282 _gst_uri_string_to_list (const gchar * str, const gchar * sep, gboolean convert,
1283     gboolean unescape)
1284 {
1285   GList *new_list = NULL;
1286
1287   if (str) {
1288     guint pct_sep_len = 0;
1289     gchar *pct_sep = NULL;
1290     gchar **split_str;
1291
1292     if (convert && !unescape) {
1293       pct_sep = g_strdup_printf ("%%%2.2X", (guint) (*sep));
1294       pct_sep_len = 3;
1295     }
1296
1297     split_str = g_strsplit (str, sep, -1);
1298     if (split_str) {
1299       gchar **next_elem;
1300       for (next_elem = split_str; *next_elem; next_elem += 1) {
1301         gchar *elem = *next_elem;
1302         if (*elem == '\0') {
1303           new_list = g_list_append (new_list, NULL);
1304         } else {
1305           if (convert && !unescape) {
1306             gchar *next_sep;
1307             for (next_sep = strcasestr (elem, pct_sep); next_sep;
1308                 next_sep = strcasestr (next_sep + 1, pct_sep)) {
1309               *next_sep = *sep;
1310               memmove (next_sep + 1, next_sep + pct_sep_len,
1311                   strlen (next_sep + pct_sep_len) + 1);
1312             }
1313           }
1314           if (unescape) {
1315             *next_elem = g_uri_unescape_string (elem, NULL);
1316             g_free (elem);
1317             elem = *next_elem;
1318           }
1319           new_list = g_list_append (new_list, g_strdup (elem));
1320         }
1321       }
1322     }
1323     g_strfreev (split_str);
1324     if (convert && !unescape)
1325       g_free (pct_sep);
1326   }
1327
1328   return new_list;
1329 }
1330
1331 static GHashTable *
1332 _gst_uri_string_to_table (const gchar * str, const gchar * part_sep,
1333     const gchar * kv_sep, gboolean convert, gboolean unescape)
1334 {
1335   GHashTable *new_table = NULL;
1336
1337   if (str) {
1338     gchar *pct_part_sep = NULL, *pct_kv_sep = NULL;
1339     gchar **split_parts;
1340
1341     new_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1342
1343     if (convert && !unescape) {
1344       pct_part_sep = g_strdup_printf ("%%%2.2X", (guint) (*part_sep));
1345       pct_kv_sep = g_strdup_printf ("%%%2.2X", (guint) (*kv_sep));
1346     }
1347
1348     split_parts = g_strsplit (str, part_sep, -1);
1349     if (split_parts) {
1350       gchar **next_part;
1351       for (next_part = split_parts; *next_part; next_part += 1) {
1352         gchar *part = *next_part;
1353         gchar *kv_sep_pos;
1354         gchar *key, *value;
1355         /* if we are converting percent encoded versions of separators then
1356          *  substitute the part separator now. */
1357         if (convert && !unescape) {
1358           gchar *next_sep;
1359           for (next_sep = strcasestr (part, pct_part_sep); next_sep;
1360               next_sep = strcasestr (next_sep + 1, pct_part_sep)) {
1361             *next_sep = *part_sep;
1362             memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1);
1363           }
1364         }
1365         /* find the key/value separator within the part */
1366         kv_sep_pos = g_strstr_len (part, -1, kv_sep);
1367         if (kv_sep_pos == NULL) {
1368           if (unescape) {
1369             key = g_uri_unescape_string (part, NULL);
1370           } else {
1371             key = g_strdup (part);
1372           }
1373           value = NULL;
1374         } else {
1375           if (unescape) {
1376             key = g_uri_unescape_segment (part, kv_sep_pos, NULL);
1377             value = g_uri_unescape_string (kv_sep_pos + 1, NULL);
1378           } else {
1379             key = g_strndup (part, kv_sep_pos - part);
1380             value = g_strdup (kv_sep_pos + 1);
1381           }
1382         }
1383         /* if we are converting percent encoded versions of separators then
1384          *  substitute the key/value separator in both key and value now. */
1385         if (convert && !unescape) {
1386           gchar *next_sep;
1387           for (next_sep = strcasestr (key, pct_kv_sep); next_sep;
1388               next_sep = strcasestr (next_sep + 1, pct_kv_sep)) {
1389             *next_sep = *kv_sep;
1390             memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1);
1391           }
1392           if (value) {
1393             for (next_sep = strcasestr (value, pct_kv_sep); next_sep;
1394                 next_sep = strcasestr (next_sep + 1, pct_kv_sep)) {
1395               *next_sep = *kv_sep;
1396               memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1);
1397             }
1398           }
1399         }
1400         /* add value to the table */
1401         g_hash_table_insert (new_table, key, value);
1402       }
1403     }
1404     /* tidy up */
1405     g_strfreev (split_parts);
1406     if (convert && !unescape) {
1407       g_free (pct_part_sep);
1408       g_free (pct_kv_sep);
1409     }
1410   }
1411
1412   return new_table;
1413 }
1414
1415
1416 /*
1417  * Method definitions.
1418  */
1419
1420 /**
1421  * gst_uri_new:
1422  * @scheme: (nullable): The scheme for the new URI.
1423  * @userinfo: (nullable): The user-info for the new URI.
1424  * @host: (nullable): The host name for the new URI.
1425  * @port: The port number for the new URI or %GST_URI_NO_PORT.
1426  * @path: (nullable): The path for the new URI with '/' separating path
1427  *                      elements.
1428  * @query: (nullable): The query string for the new URI with '&' separating
1429  *                       query elements. Elements containing '&' characters
1430  *                       should encode them as "&percnt;26".
1431  * @fragment: (nullable): The fragment name for the new URI.
1432  *
1433  * Creates a new #GstUri object with the given URI parts. The path and query
1434  * strings will be broken down into their elements. All strings should not be
1435  * escaped except where indicated.
1436  *
1437  * Returns: (transfer full): A new #GstUri object.
1438  *
1439  * Since: 1.6
1440  */
1441 GstUri *
1442 gst_uri_new (const gchar * scheme, const gchar * userinfo, const gchar * host,
1443     guint port, const gchar * path, const gchar * query, const gchar * fragment)
1444 {
1445   GstUri *new_uri;
1446
1447   new_uri = _gst_uri_new ();
1448   if (new_uri) {
1449     new_uri->scheme = g_strdup (scheme);
1450     new_uri->userinfo = g_strdup (userinfo);
1451     new_uri->host = g_strdup (host);
1452     new_uri->port = port;
1453     new_uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE);
1454     new_uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, FALSE);
1455     new_uri->fragment = g_strdup (fragment);
1456   }
1457
1458   return new_uri;
1459 }
1460
1461 /**
1462  * gst_uri_new_with_base:
1463  * @base: (transfer none)(nullable): The base URI to join the new URI to.
1464  * @scheme: (nullable): The scheme for the new URI.
1465  * @userinfo: (nullable): The user-info for the new URI.
1466  * @host: (nullable): The host name for the new URI.
1467  * @port: The port number for the new URI or %GST_URI_NO_PORT.
1468  * @path: (nullable): The path for the new URI with '/' separating path
1469  *                      elements.
1470  * @query: (nullable): The query string for the new URI with '&' separating
1471  *                       query elements. Elements containing '&' characters
1472  *                       should encode them as "&percnt;26".
1473  * @fragment: (nullable): The fragment name for the new URI.
1474  *
1475  * Like gst_uri_new(), but joins the new URI onto a base URI.
1476  *
1477  * Returns: (transfer full): The new URI joined onto @base.
1478  *
1479  * Since: 1.6
1480  */
1481 GstUri *
1482 gst_uri_new_with_base (GstUri * base, const gchar * scheme,
1483     const gchar * userinfo, const gchar * host, guint port, const gchar * path,
1484     const gchar * query, const gchar * fragment)
1485 {
1486   GstUri *new_rel_uri;
1487   GstUri *new_uri;
1488
1489   g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL);
1490
1491   new_rel_uri = gst_uri_new (scheme, userinfo, host, port, path, query,
1492       fragment);
1493   new_uri = gst_uri_join (base, new_rel_uri);
1494   gst_uri_unref (new_rel_uri);
1495
1496   return new_uri;
1497 }
1498
1499 /**
1500  * gst_uri_from_string:
1501  * @uri: The URI string to parse.
1502  *
1503  * Parses a URI string into a new #GstUri object. Will return NULL if the URI
1504  * cannot be parsed.
1505  *
1506  * Returns: (transfer full)(nullable): A new #GstUri object, or NULL.
1507  *
1508  * Since: 1.6
1509  */
1510 GstUri *
1511 gst_uri_from_string (const gchar * uri)
1512 {
1513   const gchar *orig_uri = uri;
1514   GstUri *uri_obj;
1515
1516   uri_obj = _gst_uri_new ();
1517
1518   if (uri_obj && uri != NULL) {
1519     int i = 0;
1520
1521     /* be helpful and skip initial white space */
1522     while (*uri == '\v' || g_ascii_isspace (*uri))
1523       uri++;
1524
1525     if (g_ascii_isalpha (uri[i])) {
1526       /* find end of scheme name */
1527       i++;
1528       while (g_ascii_isalnum (uri[i]) || uri[i] == '+' || uri[i] == '-' ||
1529           uri[i] == '.')
1530         i++;
1531     }
1532     if (i > 0 && uri[i] == ':') {
1533       /* get scheme */
1534       uri_obj->scheme = g_strndup (uri, i);
1535       uri += i + 1;
1536     }
1537     if (uri[0] == '/' && uri[1] == '/') {
1538       const gchar *eoa, *eoui, *eoh, *reoh;
1539       /* get authority [userinfo@]host[:port] */
1540       uri += 2;
1541       /* find end of authority */
1542       eoa = uri + strcspn (uri, "/?#");
1543
1544       /* find end of userinfo */
1545       eoui = strchr (uri, '@');
1546       if (eoui != NULL && eoui < eoa) {
1547         uri_obj->userinfo = g_uri_unescape_segment (uri, eoui, NULL);
1548         uri = eoui + 1;
1549       }
1550       /* find end of host */
1551       if (uri[0] == '[') {
1552         eoh = strchr (uri, ']');
1553         if (eoh == NULL || eoh > eoa) {
1554           GST_DEBUG ("Unable to parse the host part of the URI '%s'.",
1555               orig_uri);
1556           _gst_uri_free (uri_obj);
1557           return NULL;
1558         }
1559         reoh = eoh + 1;
1560         uri++;
1561       } else {
1562         reoh = eoh = strchr (uri, ':');
1563         if (eoh == NULL || eoh > eoa)
1564           reoh = eoh = eoa;
1565       }
1566       /* don't capture empty host strings */
1567       if (eoh != uri)
1568         uri_obj->host = g_uri_unescape_segment (uri, eoh, NULL);
1569
1570       uri = reoh;
1571       if (uri < eoa) {
1572         /* if port number is malformed then we can't parse this */
1573         if (uri[0] != ':' || strspn (uri + 1, "0123456789") != eoa - uri - 1) {
1574           GST_DEBUG ("Unable to parse host/port part of the URI '%s'.",
1575               orig_uri);
1576           _gst_uri_free (uri_obj);
1577           return NULL;
1578         }
1579         /* otherwise treat port as unsigned decimal number */
1580         uri++;
1581         while (uri < eoa) {
1582           uri_obj->port = uri_obj->port * 10 + g_ascii_digit_value (*uri);
1583           uri++;
1584         }
1585       }
1586       uri = eoa;
1587     }
1588     if (uri != NULL && uri[0] != '\0') {
1589       /* get path */
1590       size_t len;
1591       len = strcspn (uri, "?#");
1592       if (uri[len] == '\0') {
1593         uri_obj->path = _gst_uri_string_to_list (uri, "/", FALSE, TRUE);
1594         uri = NULL;
1595       } else {
1596         if (len > 0) {
1597           gchar *path_str = g_strndup (uri, len);
1598           uri_obj->path = _gst_uri_string_to_list (path_str, "/", FALSE, TRUE);
1599           g_free (path_str);
1600         }
1601         uri += len;
1602       }
1603     }
1604     if (uri != NULL && uri[0] == '?') {
1605       /* get query */
1606       gchar *eoq;
1607       eoq = strchr (++uri, '#');
1608       if (eoq == NULL) {
1609         uri_obj->query = _gst_uri_string_to_table (uri, "&", "=", TRUE, TRUE);
1610         uri = NULL;
1611       } else {
1612         if (eoq != uri) {
1613           gchar *query_str = g_strndup (uri, eoq - uri);
1614           uri_obj->query = _gst_uri_string_to_table (query_str, "&", "=", TRUE,
1615               TRUE);
1616           g_free (query_str);
1617         }
1618         uri = eoq;
1619       }
1620     }
1621     if (uri != NULL && uri[0] == '#') {
1622       uri_obj->fragment = g_uri_unescape_string (uri + 1, NULL);
1623     }
1624   }
1625
1626   return uri_obj;
1627 }
1628
1629 /**
1630  * gst_uri_from_string_with_base:
1631  * @base: (transfer none)(nullable): The base URI to join the new URI with.
1632  * @uri: The URI string to parse.
1633  *
1634  * Like gst_uri_from_string() but also joins with a base URI.
1635  *
1636  * Returns: (transfer full): A new #GstUri object.
1637  *
1638  * Since: 1.6
1639  */
1640 GstUri *
1641 gst_uri_from_string_with_base (GstUri * base, const gchar * uri)
1642 {
1643   GstUri *new_rel_uri;
1644   GstUri *new_uri;
1645
1646   g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL);
1647
1648   new_rel_uri = gst_uri_from_string (uri);
1649   new_uri = gst_uri_join (base, new_rel_uri);
1650   gst_uri_unref (new_rel_uri);
1651
1652   return new_uri;
1653 }
1654
1655 /**
1656  * gst_uri_equal:
1657  * @first: First #GstUri to compare.
1658  * @second: Second #GstUri to compare.
1659  *
1660  * Compares two #GstUri objects to see if they represent the same normalized
1661  * URI.
1662  *
1663  * Returns: %TRUE if the normalized versions of the two URI's would be equal.
1664  *
1665  * Since: 1.6
1666  */
1667 gboolean
1668 gst_uri_equal (const GstUri * first, const GstUri * second)
1669 {
1670   gchar *first_norm = NULL, *second_norm = NULL;
1671   GList *first_norm_list = NULL, *second_norm_list = NULL;
1672   const gchar *first_cmp, *second_cmp;
1673   GHashTableIter table_iter;
1674   gpointer key, value;
1675   int result;
1676
1677   g_return_val_if_fail ((first == NULL || GST_IS_URI (first)) &&
1678       (second == NULL || GST_IS_URI (second)), FALSE);
1679
1680   if (first == second)
1681     return TRUE;
1682
1683   if (first == NULL || second == NULL)
1684     return FALSE;
1685
1686   if (first->port != second->port)
1687     return FALSE;
1688
1689 /* work out a version of field value (normalized or not) to compare.
1690  * first_cmp, second_cmp will be the values to compare later.
1691  * first_norm, second_norm will be non-NULL if normalized versions are used,
1692  *  and need to be freed later.
1693  */
1694 #define GST_URI_NORMALIZED_FIELD(pos, field, norm_fn, flags) \
1695   pos##_cmp = pos->field; \
1696   if (_gst_uri_first_non_normalized_char ((gchar*)pos##_cmp, flags) != NULL) { \
1697     pos##_norm = g_strdup (pos##_cmp); \
1698     norm_fn (pos##_norm); \
1699     pos##_cmp = pos##_norm; \
1700   }
1701
1702 /* compare two string values, normalizing if needed */
1703 #define GST_URI_NORMALIZED_CMP_STR(field, norm_fn, flags) \
1704   GST_URI_NORMALIZED_FIELD (first, field, norm_fn, flags) \
1705   GST_URI_NORMALIZED_FIELD (second, field, norm_fn, flags) \
1706   result = g_strcmp0 (first_cmp, second_cmp); \
1707   g_free (first_norm); \
1708   first_norm = NULL; \
1709   g_free (second_norm); \
1710   second_norm = NULL; \
1711   if (result != 0) return FALSE
1712
1713 /* compare two string values */
1714 #define GST_URI_CMP_STR(field) \
1715   if (g_strcmp0 (first->field, second->field) != 0) return FALSE
1716
1717 /* compare two GLists, normalize lists if needed before comparison */
1718 #define GST_URI_NORMALIZED_CMP_LIST(field, norm_fn, copy_fn, cmp_fn, free_fn) \
1719   first_norm_list = g_list_copy_deep (first->field, (GCopyFunc) copy_fn, NULL); \
1720   norm_fn (&first_norm_list); \
1721   second_norm_list = g_list_copy_deep (second->field, (GCopyFunc) copy_fn, NULL); \
1722   norm_fn (&second_norm_list); \
1723   result = _gst_uri_compare_lists (first_norm_list, second_norm_list, (GCompareFunc) cmp_fn); \
1724   g_list_free_full (first_norm_list, free_fn); \
1725   g_list_free_full (second_norm_list, free_fn); \
1726   if (result != 0) return FALSE
1727
1728   GST_URI_CMP_STR (userinfo);
1729
1730   GST_URI_CMP_STR (fragment);
1731
1732   GST_URI_NORMALIZED_CMP_STR (scheme, _gst_uri_normalize_scheme,
1733       _GST_URI_NORMALIZE_LOWERCASE);
1734
1735   GST_URI_NORMALIZED_CMP_STR (host, _gst_uri_normalize_hostname,
1736       _GST_URI_NORMALIZE_LOWERCASE);
1737
1738   GST_URI_NORMALIZED_CMP_LIST (path, _gst_uri_normalize_path, g_strdup,
1739       g_strcmp0, g_free);
1740
1741   if (first->query == NULL && second->query != NULL)
1742     return FALSE;
1743   if (first->query != NULL && second->query == NULL)
1744     return FALSE;
1745   if (first->query != NULL) {
1746     if (g_hash_table_size (first->query) != g_hash_table_size (second->query))
1747       return FALSE;
1748
1749     g_hash_table_iter_init (&table_iter, first->query);
1750     while (g_hash_table_iter_next (&table_iter, &key, &value)) {
1751       if (!g_hash_table_contains (second->query, key))
1752         return FALSE;
1753       result = g_strcmp0 (g_hash_table_lookup (second->query, key), value);
1754       if (result != 0)
1755         return FALSE;
1756     }
1757   }
1758 #undef GST_URI_NORMALIZED_CMP_STR
1759 #undef GST_URI_CMP_STR
1760 #undef GST_URI_NORMALIZED_CMP_LIST
1761 #undef GST_URI_NORMALIZED_FIELD
1762
1763   return TRUE;
1764 }
1765
1766 /**
1767  * gst_uri_join:
1768  * @base_uri: (transfer none)(nullable): The base URI to join another to.
1769  * @ref_uri: (transfer none)(nullable): The reference URI to join onto the
1770  *                                        base URI.
1771  *
1772  * Join a reference URI onto a base URI using the method from RFC 3986.
1773  * If either URI is %NULL then the other URI will be returned with the ref count
1774  * increased.
1775  *
1776  * Returns: (transfer full): A #GstUri which represents the base with the
1777  *                           reference URI joined on.
1778  *
1779  * Since: 1.6
1780  */
1781 GstUri *
1782 gst_uri_join (GstUri * base_uri, GstUri * ref_uri)
1783 {
1784   const gchar *r_scheme;
1785   GstUri *t;
1786
1787   g_return_val_if_fail ((base_uri == NULL || GST_IS_URI (base_uri)) &&
1788       (ref_uri == NULL || GST_IS_URI (ref_uri)), NULL);
1789
1790   if (base_uri == NULL && ref_uri == NULL)
1791     return NULL;
1792   if (base_uri == NULL) {
1793     g_return_val_if_fail (GST_IS_URI (ref_uri), NULL);
1794     return gst_uri_ref (ref_uri);
1795   }
1796   if (ref_uri == NULL) {
1797     g_return_val_if_fail (GST_IS_URI (base_uri), NULL);
1798     return gst_uri_ref (base_uri);
1799   }
1800
1801   g_return_val_if_fail (GST_IS_URI (base_uri) && GST_IS_URI (ref_uri), NULL);
1802
1803   t = _gst_uri_new ();
1804
1805   if (t == NULL)
1806     return t;
1807
1808   /* process according to RFC3986 */
1809   r_scheme = ref_uri->scheme;
1810   if (r_scheme != NULL && g_strcmp0 (base_uri->scheme, r_scheme) == 0) {
1811     r_scheme = NULL;
1812   }
1813   if (r_scheme != NULL) {
1814     t->scheme = g_strdup (r_scheme);
1815     t->userinfo = g_strdup (ref_uri->userinfo);
1816     t->host = g_strdup (ref_uri->host);
1817     t->port = ref_uri->port;
1818     t->path = _remove_dot_segments (ref_uri->path);
1819     t->query = _gst_uri_copy_query_table (ref_uri->query);
1820   } else {
1821     if (ref_uri->host != NULL) {
1822       t->userinfo = g_strdup (ref_uri->userinfo);
1823       t->host = g_strdup (ref_uri->host);
1824       t->port = ref_uri->port;
1825       t->path = _remove_dot_segments (ref_uri->path);
1826       t->query = _gst_uri_copy_query_table (ref_uri->query);
1827     } else {
1828       if (ref_uri->path == NULL) {
1829         t->path = g_list_copy_deep (base_uri->path, (GCopyFunc) g_strdup, NULL);
1830         if (ref_uri->query != NULL)
1831           t->query = _gst_uri_copy_query_table (ref_uri->query);
1832         else
1833           t->query = _gst_uri_copy_query_table (base_uri->query);
1834       } else {
1835         if (ref_uri->path->data == NULL)
1836           t->path = _remove_dot_segments (ref_uri->path);
1837         else {
1838           GList *mrgd = _merge (base_uri->path, ref_uri->path);
1839           t->path = _remove_dot_segments (mrgd);
1840           g_list_free_full (mrgd, g_free);
1841         }
1842         t->query = _gst_uri_copy_query_table (ref_uri->query);
1843       }
1844       t->userinfo = g_strdup (base_uri->userinfo);
1845       t->host = g_strdup (base_uri->host);
1846       t->port = base_uri->port;
1847     }
1848     t->scheme = g_strdup (base_uri->scheme);
1849   }
1850   t->fragment = g_strdup (ref_uri->fragment);
1851
1852   return t;
1853 }
1854
1855 /**
1856  * gst_uri_join_strings:
1857  * @base_uri: The percent-encoded base URI.
1858  * @ref_uri: The percent-encoded reference URI to join to the @base_uri.
1859  *
1860  * This is a convenience function to join two URI strings and return the result.
1861  * The returned string should be g_free()'d after use.
1862  *
1863  * Returns: (transfer full): A string representing the percent-encoded join of
1864  *          the two URIs.
1865  *
1866  * Since: 1.6
1867  */
1868 gchar *
1869 gst_uri_join_strings (const gchar * base_uri, const gchar * ref_uri)
1870 {
1871   GstUri *base, *result;
1872   gchar *result_uri;
1873
1874   base = gst_uri_from_string (base_uri);
1875   result = gst_uri_from_string_with_base (base, ref_uri);
1876   result_uri = gst_uri_to_string (result);
1877   gst_uri_unref (base);
1878   gst_uri_unref (result);
1879
1880   return result_uri;
1881 }
1882
1883 /**
1884  * gst_uri_is_writable:
1885  * @uri: The #GstUri object to test.
1886  *
1887  * Check if it is safe to write to this #GstUri.
1888  *
1889  * Check if the refcount of @uri is exactly 1, meaning that no other
1890  * reference exists to the #GstUri and that the #GstUri is therefore writable.
1891  *
1892  * Modification of a #GstUri should only be done after verifying that it is
1893  * writable.
1894  *
1895  * Returns: %TRUE if it is safe to write to the object.
1896  *
1897  * Since: 1.6
1898  */
1899 gboolean
1900 gst_uri_is_writable (const GstUri * uri)
1901 {
1902   g_return_val_if_fail (GST_IS_URI (uri), FALSE);
1903   return gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (uri));
1904 }
1905
1906 /**
1907  * gst_uri_make_writable:
1908  * @uri: (transfer full): The #GstUri object to make writable.
1909  *
1910  * Make the #GstUri writable.
1911  *
1912  * Checks if @uri is writable, and if so the original object is returned. If
1913  * not, then a writable copy is made and returned. This gives away the
1914  * reference to @uri and returns a reference to the new #GstUri.
1915  * If @uri is %NULL then %NULL is returned.
1916  *
1917  * Returns: (transfer full): A writable version of @uri.
1918  *
1919  * Since: 1.6
1920  */
1921 GstUri *
1922 gst_uri_make_writable (GstUri * uri)
1923 {
1924   g_return_val_if_fail (GST_IS_URI (uri), NULL);
1925   return
1926       GST_URI_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (uri)));
1927 }
1928
1929 /**
1930  * gst_uri_to_string:
1931  * @uri: This #GstUri to convert to a string.
1932  *
1933  * Convert the URI to a string.
1934  *
1935  * Returns the URI as held in this object as a #gchar* nul-terminated string.
1936  * The caller should g_free() the string once they are finished with it.
1937  * The string is put together as described in RFC 3986.
1938  *
1939  * Returns: (transfer full): The string version of the URI.
1940  *
1941  * Since: 1.6
1942  */
1943 gchar *
1944 gst_uri_to_string (const GstUri * uri)
1945 {
1946   GString *uri_str;
1947   gchar *escaped;
1948
1949   g_return_val_if_fail (GST_IS_URI (uri), NULL);
1950
1951   uri_str = g_string_new (NULL);
1952
1953   if (uri->scheme != NULL)
1954     g_string_append_printf (uri_str, "%s:", uri->scheme);
1955
1956   if (uri->userinfo != NULL || uri->host != NULL ||
1957       uri->port != GST_URI_NO_PORT)
1958     g_string_append (uri_str, "//");
1959
1960   if (uri->userinfo != NULL) {
1961     escaped = _gst_uri_escape_userinfo (uri->userinfo);
1962     g_string_append_printf (uri_str, "%s@", escaped);
1963     g_free (escaped);
1964   }
1965
1966   if (uri->host != NULL) {
1967     if (strchr (uri->host, ':') != NULL) {
1968       escaped = _gst_uri_escape_host_colon (uri->host);
1969       g_string_append_printf (uri_str, "[%s]", escaped);
1970       g_free (escaped);
1971     } else {
1972       escaped = _gst_uri_escape_host (uri->host);
1973       g_string_append (uri_str, escaped);
1974       g_free (escaped);
1975     }
1976   }
1977
1978   if (uri->port != GST_URI_NO_PORT)
1979     g_string_append_printf (uri_str, ":%u", uri->port);
1980
1981   if (uri->path != NULL) {
1982     escaped = gst_uri_get_path_string (uri);
1983     g_string_append (uri_str, escaped);
1984     g_free (escaped);
1985   }
1986
1987   if (uri->query) {
1988     g_string_append (uri_str, "?");
1989     escaped = gst_uri_get_query_string (uri);
1990     g_string_append (uri_str, escaped);
1991     g_free (escaped);
1992   }
1993
1994   if (uri->fragment != NULL) {
1995     escaped = _gst_uri_escape_fragment (uri->fragment);
1996     g_string_append_printf (uri_str, "#%s", escaped);
1997     g_free (escaped);
1998   }
1999
2000   return g_string_free (uri_str, FALSE);
2001 }
2002
2003 /**
2004  * gst_uri_is_normalized:
2005  * @uri: The #GstUri to test to see if it is normalized.
2006  *
2007  * Tests the @uri to see if it is normalized. A %NULL @uri is considered to be
2008  * normalized.
2009  *
2010  * Returns: TRUE if the URI is normalized or is %NULL.
2011  *
2012  * Since: 1.6
2013  */
2014 gboolean
2015 gst_uri_is_normalized (const GstUri * uri)
2016 {
2017   GList *new_path;
2018   gboolean ret;
2019
2020   if (uri == NULL)
2021     return TRUE;
2022
2023   g_return_val_if_fail (GST_IS_URI (uri), FALSE);
2024
2025   /* check for non-normalized characters in uri parts */
2026   if (_gst_uri_first_non_normalized_char (uri->scheme,
2027           _GST_URI_NORMALIZE_LOWERCASE) != NULL ||
2028       /*_gst_uri_first_non_normalized_char (uri->userinfo,
2029           _GST_URI_NORMALIZE_PERCENTAGES) != NULL || */
2030       _gst_uri_first_non_normalized_char (uri->host,
2031           _GST_URI_NORMALIZE_LOWERCASE /*| _GST_URI_NORMALIZE_PERCENTAGES */ )
2032       != NULL
2033       /*|| _gst_uri_first_non_normalized_char (uri->path,
2034          _GST_URI_NORMALIZE_PERCENTAGES) != NULL
2035          || _gst_uri_first_non_normalized_char (uri->query,
2036          _GST_URI_NORMALIZE_PERCENTAGES) != NULL
2037          || _gst_uri_first_non_normalized_char (uri->fragment,
2038          _GST_URI_NORMALIZE_PERCENTAGES) != NULL */ )
2039     return FALSE;
2040
2041   /* also check path has had dot segments removed */
2042   new_path = _remove_dot_segments (uri->path);
2043   ret =
2044       (_gst_uri_compare_lists (new_path, uri->path,
2045           (GCompareFunc) g_strcmp0) == 0);
2046   g_list_free_full (new_path, g_free);
2047   return ret;
2048 }
2049
2050 /**
2051  * gst_uri_normalize:
2052  * @uri: (transfer none): The #GstUri to normalize.
2053  *
2054  * Normalization will remove extra path segments ("." and "..") from the URI. It
2055  * will also convert the scheme and host name to lower case and any
2056  * percent-encoded values to uppercase.
2057  *
2058  * The #GstUri object must be writable. Check with gst_uri_is_writable() or use
2059  * gst_uri_make_writable() first.
2060  *
2061  * Returns: TRUE if the URI was modified.
2062  *
2063  * Since: 1.6
2064  */
2065 gboolean
2066 gst_uri_normalize (GstUri * uri)
2067 {
2068   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2069
2070   return _gst_uri_normalize_scheme (uri->scheme) |
2071       _gst_uri_normalize_userinfo (uri->userinfo) |
2072       _gst_uri_normalize_hostname (uri->host) |
2073       _gst_uri_normalize_path (&uri->path) |
2074       _gst_uri_normalize_query (uri->query) |
2075       _gst_uri_normalize_fragment (uri->fragment);
2076 }
2077
2078 /**
2079  * gst_uri_get_scheme:
2080  * @uri: (nullable): This #GstUri object.
2081  *
2082  * Get the scheme name from the URI or %NULL if it doesn't exist.
2083  * If @uri is %NULL then returns %NULL.
2084  *
2085  * Returns: The scheme from the #GstUri object or %NULL.
2086  */
2087 const gchar *
2088 gst_uri_get_scheme (const GstUri * uri)
2089 {
2090   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2091   return (uri ? uri->scheme : NULL);
2092 }
2093
2094 /**
2095  * gst_uri_set_scheme:
2096  * @uri: (transfer none)(nullable): The #GstUri to modify.
2097  * @scheme: The new scheme to set or %NULL to unset the scheme.
2098  *
2099  * Set or unset the scheme for the URI.
2100  *
2101  * Returns: %TRUE if the scheme was set/unset successfully.
2102  *
2103  * Since: 1.6
2104  */
2105 gboolean
2106 gst_uri_set_scheme (GstUri * uri, const gchar * scheme)
2107 {
2108   if (!uri)
2109     return scheme == NULL;
2110   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2111
2112   g_free (uri->scheme);
2113   uri->scheme = g_strdup (scheme);
2114
2115   return TRUE;
2116 }
2117
2118 /**
2119  * gst_uri_get_userinfo:
2120  * @uri: (nullable): This #GstUri object.
2121  *
2122  * Get the userinfo (usually in the form "username:password") from the URI
2123  * or %NULL if it doesn't exist. If @uri is %NULL then returns %NULL.
2124  *
2125  * Returns: The userinfo from the #GstUri object or %NULL.
2126  *
2127  * Since: 1.6
2128  */
2129 const gchar *
2130 gst_uri_get_userinfo (const GstUri * uri)
2131 {
2132   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2133   return (uri ? uri->userinfo : NULL);
2134 }
2135
2136 /**
2137  * gst_uri_set_userinfo:
2138  * @uri: (transfer none)(nullable): The #GstUri to modify.
2139  * @userinfo: The new user-information string to set or %NULL to unset.
2140  *
2141  * Set or unset the user information for the URI.
2142  *
2143  * Returns: %TRUE if the user information was set/unset successfully.
2144  *
2145  * Since: 1.6
2146  */
2147 gboolean
2148 gst_uri_set_userinfo (GstUri * uri, const gchar * userinfo)
2149 {
2150   if (!uri)
2151     return userinfo == NULL;
2152   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2153
2154   g_free (uri->userinfo);
2155   uri->userinfo = g_strdup (userinfo);
2156
2157   return TRUE;
2158 }
2159
2160 /**
2161  * gst_uri_get_host:
2162  * @uri: (nullable): This #GstUri object.
2163  *
2164  * Get the host name from the URI or %NULL if it doesn't exist.
2165  * If @uri is %NULL then returns %NULL.
2166  *
2167  * Returns: The host name from the #GstUri object or %NULL.
2168  *
2169  * Since: 1.6
2170  */
2171 const gchar *
2172 gst_uri_get_host (const GstUri * uri)
2173 {
2174   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2175   return (uri ? uri->host : NULL);
2176 }
2177
2178 /**
2179  * gst_uri_set_host:
2180  * @uri: (transfer none)(nullable): The #GstUri to modify.
2181  * @host: The new host string to set or %NULL to unset.
2182  *
2183  * Set or unset the host for the URI.
2184  *
2185  * Returns: %TRUE if the host was set/unset successfully.
2186  *
2187  * Since: 1.6
2188  */
2189 gboolean
2190 gst_uri_set_host (GstUri * uri, const gchar * host)
2191 {
2192   if (!uri)
2193     return host == NULL;
2194   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2195
2196   g_free (uri->host);
2197   uri->host = g_strdup (host);
2198
2199   return TRUE;
2200 }
2201
2202 /**
2203  * gst_uri_get_port:
2204  * @uri: (nullable): This #GstUri object.
2205  *
2206  * Get the port number from the URI or %GST_URI_NO_PORT if it doesn't exist.
2207  * If @uri is %NULL then returns %GST_URI_NO_PORT.
2208  *
2209  * Returns: The port number from the #GstUri object or %GST_URI_NO_PORT.
2210  *
2211  * Since: 1.6
2212  */
2213 guint
2214 gst_uri_get_port (const GstUri * uri)
2215 {
2216   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), GST_URI_NO_PORT);
2217   return (uri ? uri->port : GST_URI_NO_PORT);
2218 }
2219
2220 /**
2221  * gst_uri_set_port:
2222  * @uri: (transfer none)(nullable): The #GstUri to modify.
2223  * @port: The new port number to set or %GST_URI_NO_PORT to unset.
2224  *
2225  * Set or unset the port number for the URI.
2226  *
2227  * Returns: %TRUE if the port number was set/unset successfully.
2228  *
2229  * Since: 1.6
2230  */
2231 gboolean
2232 gst_uri_set_port (GstUri * uri, guint port)
2233 {
2234   if (!uri)
2235     return port == GST_URI_NO_PORT;
2236   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2237
2238   uri->port = port;
2239
2240   return TRUE;
2241 }
2242
2243 /**
2244  * gst_uri_get_path:
2245  * @uri: The #GstUri to get the path from.
2246  *
2247  * Extract the path string from the URI object.
2248  *
2249  * Returns: (transfer full): The path from the URI. Once finished with the
2250  *                           string should be g_free()'d.
2251  *
2252  * Since: 1.6
2253  */
2254 gchar *
2255 gst_uri_get_path (const GstUri * uri)
2256 {
2257   GList *path_segment;
2258   const gchar *sep = "";
2259   GString *ret;
2260
2261   if (!uri)
2262     return NULL;
2263   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2264   if (!uri->path)
2265     return NULL;
2266
2267   ret = g_string_new (NULL);
2268
2269   for (path_segment = uri->path; path_segment;
2270       path_segment = path_segment->next) {
2271     g_string_append (ret, sep);
2272     if (path_segment->data) {
2273       g_string_append (ret, path_segment->data);
2274     }
2275     sep = "/";
2276   }
2277
2278   return g_string_free (ret, FALSE);
2279 }
2280
2281 /**
2282  * gst_uri_set_path:
2283  * @uri: (transfer none)(nullable): The #GstUri to modify.
2284  * @path: The new path to set with path segments separated by '/', or use %NULL
2285  *        to unset the path.
2286  *
2287  * Sets or unsets the path in the URI.
2288  *
2289  * Returns: %TRUE if the path was set successfully.
2290  *
2291  * Since: 1.6
2292  */
2293 gboolean
2294 gst_uri_set_path (GstUri * uri, const gchar * path)
2295 {
2296   if (!uri)
2297     return path == NULL;
2298   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2299
2300   g_list_free_full (uri->path, g_free);
2301   uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE);
2302
2303   return TRUE;
2304 }
2305
2306 /**
2307  * gst_uri_get_path_string:
2308  * @uri: The #GstUri to get the path from.
2309  *
2310  * Extract the path string from the URI object as a percent encoded URI path.
2311  *
2312  * Returns: (transfer full): The path from the URI. Once finished with the
2313  *                           string should be g_free()'d.
2314  *
2315  * Since: 1.6
2316  */
2317 gchar *
2318 gst_uri_get_path_string (const GstUri * uri)
2319 {
2320   GList *path_segment;
2321   const gchar *sep = "";
2322   GString *ret;
2323   gchar *escaped;
2324
2325   if (!uri)
2326     return NULL;
2327   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2328   if (!uri->path)
2329     return NULL;
2330
2331   ret = g_string_new (NULL);
2332
2333   for (path_segment = uri->path; path_segment;
2334       path_segment = path_segment->next) {
2335     g_string_append (ret, sep);
2336     if (path_segment->data) {
2337       escaped = _gst_uri_escape_path_segment (path_segment->data);
2338       g_string_append (ret, escaped);
2339       g_free (escaped);
2340     }
2341     sep = "/";
2342   }
2343
2344   return g_string_free (ret, FALSE);
2345 }
2346
2347 /**
2348  * gst_uri_set_path_string:
2349  * @uri: (transfer none)(nullable): The #GstUri to modify.
2350  * @path: The new percent encoded path to set with path segments separated by
2351  * '/', or use %NULL to unset the path.
2352  *
2353  * Sets or unsets the path in the URI.
2354  *
2355  * Returns: %TRUE if the path was set successfully.
2356  *
2357  * Since: 1.6
2358  */
2359 gboolean
2360 gst_uri_set_path_string (GstUri * uri, const gchar * path)
2361 {
2362   if (!uri)
2363     return path == NULL;
2364   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2365
2366   g_list_free_full (uri->path, g_free);
2367   uri->path = _gst_uri_string_to_list (path, "/", FALSE, TRUE);
2368   return TRUE;
2369 }
2370
2371 /**
2372  * gst_uri_get_path_segments:
2373  * @uri: (nullable): The #GstUri to get the path from.
2374  *
2375  * Get a list of path segments from the URI.
2376  *
2377  * Returns: (transfer full)(element-type gchar*): A #GList of path segment
2378  *          strings or %NULL if no path segments are available. Free the list
2379  *          when no longer needed with g_list_free_full(list, g_free).
2380  *
2381  * Since: 1.6
2382  */
2383 GList *
2384 gst_uri_get_path_segments (const GstUri * uri)
2385 {
2386   GList *ret = NULL;
2387
2388   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2389
2390   if (uri) {
2391     ret = g_list_copy_deep (uri->path, (GCopyFunc) g_strdup, NULL);
2392   }
2393
2394   return ret;
2395 }
2396
2397 /**
2398  * gst_uri_set_path_segments:
2399  * @uri: (transfer none)(nullable): The #GstUri to modify.
2400  * @path_segments: (transfer full)(nullable)(element-type gchar*): The new
2401  *                 path list to set.
2402  *
2403  * Replace the path segments list in the URI.
2404  *
2405  * Returns: %TRUE if the path segments were set successfully.
2406  *
2407  * Since: 1.6
2408  */
2409 gboolean
2410 gst_uri_set_path_segments (GstUri * uri, GList * path_segments)
2411 {
2412   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), FALSE);
2413
2414   if (!uri) {
2415     if (path_segments)
2416       g_list_free_full (path_segments, g_free);
2417     return path_segments == NULL;
2418   }
2419
2420   g_return_val_if_fail (gst_uri_is_writable (uri), FALSE);
2421
2422   g_list_free_full (uri->path, g_free);
2423   uri->path = path_segments;
2424   return TRUE;
2425 }
2426
2427 /**
2428  * gst_uri_append_path:
2429  * @uri: (transfer none)(nullable): The #GstUri to modify.
2430  * @relative_path: Relative path to append to the end of the current path.
2431  *
2432  * Append a path onto the end of the path in the URI. The path is not
2433  * normalized, call #gst_uri_normalize() to normalize the path.
2434  *
2435  * Returns: %TRUE if the path was appended successfully.
2436  *
2437  * Since: 1.6
2438  */
2439 gboolean
2440 gst_uri_append_path (GstUri * uri, const gchar * relative_path)
2441 {
2442   GList *rel_path_list;
2443
2444   if (!uri)
2445     return relative_path == NULL;
2446   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2447   if (!relative_path)
2448     return TRUE;
2449
2450   if (uri->path) {
2451     GList *last_elem = g_list_last (uri->path);
2452     if (last_elem->data == NULL) {
2453       uri->path = g_list_delete_link (uri->path, last_elem);
2454     }
2455   }
2456   rel_path_list = _gst_uri_string_to_list (relative_path, "/", FALSE, FALSE);
2457   /* if path was absolute, make it relative by removing initial NULL element */
2458   if (rel_path_list && rel_path_list->data == NULL) {
2459     rel_path_list = g_list_delete_link (rel_path_list, rel_path_list);
2460   }
2461   uri->path = g_list_concat (uri->path, rel_path_list);
2462   return TRUE;
2463 }
2464
2465 /**
2466  * gst_uri_append_path_segment:
2467  * @uri: (transfer none)(nullable): The #GstUri to modify.
2468  * @path_segment: The path segment string to append to the URI path.
2469  *
2470  * Append a single path segment onto the end of the URI path.
2471  *
2472  * Returns: %TRUE if the path was appended successfully.
2473  *
2474  * Since: 1.6
2475  */
2476 gboolean
2477 gst_uri_append_path_segment (GstUri * uri, const gchar * path_segment)
2478 {
2479   if (!uri)
2480     return path_segment == NULL;
2481   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2482   if (!path_segment)
2483     return TRUE;
2484
2485   /* if base path ends in a directory (i.e. last element is NULL), remove it */
2486   if (uri->path && g_list_last (uri->path)->data == NULL) {
2487     uri->path = g_list_delete_link (uri->path, g_list_last (uri->path));
2488   }
2489   uri->path = g_list_append (uri->path, g_strdup (path_segment));
2490   return TRUE;
2491 }
2492
2493 /**
2494  * gst_uri_get_query_string:
2495  * @uri: (nullable): The #GstUri to get the query string from.
2496  *
2497  * Get a percent encoded URI query string from the @uri.
2498  *
2499  * Returns: (transfer full): A percent encoded query string. Use g_free() when
2500  *          no longer needed.
2501  *
2502  * Since: 1.6
2503  */
2504 gchar *
2505 gst_uri_get_query_string (const GstUri * uri)
2506 {
2507   GHashTableIter iter;
2508   gpointer key, value;
2509   const gchar *sep = "";
2510   gchar *escaped;
2511   GString *ret;
2512
2513   if (!uri)
2514     return NULL;
2515   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2516   if (!uri->query)
2517     return NULL;
2518
2519   ret = g_string_new (NULL);
2520   g_hash_table_iter_init (&iter, uri->query);
2521   while (g_hash_table_iter_next (&iter, &key, &value)) {
2522     g_string_append (ret, sep);
2523     escaped = _gst_uri_escape_http_query_element (key);
2524     g_string_append (ret, escaped);
2525     g_free (escaped);
2526     if (value) {
2527       escaped = _gst_uri_escape_http_query_element (value);
2528       g_string_append_printf (ret, "=%s", escaped);
2529       g_free (escaped);
2530     }
2531     sep = "&";
2532   }
2533
2534   return g_string_free (ret, FALSE);
2535 }
2536
2537 /**
2538  * gst_uri_set_query_string:
2539  * @uri: (transfer none)(nullable): The #GstUri to modify.
2540  * @query: The new percent encoded query string to use to populate the query
2541  *        table, or use %NULL to unset the query table.
2542  *
2543  * Sets or unsets the query table in the URI.
2544  *
2545  * Returns: %TRUE if the query table was set successfully.
2546  *
2547  * Since: 1.6
2548  */
2549 gboolean
2550 gst_uri_set_query_string (GstUri * uri, const gchar * query)
2551 {
2552   if (!uri)
2553     return query == NULL;
2554
2555   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2556
2557   if (uri->query)
2558     g_hash_table_unref (uri->query);
2559   uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, TRUE);
2560
2561   return TRUE;
2562 }
2563
2564 /**
2565  * gst_uri_get_query_table:
2566  * @uri: (nullable): The #GstUri to get the query table from.
2567  *
2568  * Get the query table from the URI. Keys and values in the table are freed
2569  * with g_free when they are deleted. A value may be %NULL to indicate that
2570  * the key should appear in the query string in the URI, but does not have a
2571  * value. Free the returned #GHashTable with #g_hash_table_unref() when it is
2572  * no longer required. Modifying this hash table will modify the query in the
2573  * URI.
2574  *
2575  * Returns: (transfer full)(element-type gchar* gchar*): The query hash table
2576  *          from the URI.
2577  *
2578  * Since: 1.6
2579  */
2580 GHashTable *
2581 gst_uri_get_query_table (const GstUri * uri)
2582 {
2583   if (!uri)
2584     return NULL;
2585   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2586   if (!uri->query)
2587     return NULL;
2588
2589   return g_hash_table_ref (uri->query);
2590 }
2591
2592 /**
2593  * gst_uri_set_query_table:
2594  * @uri: (transfer none)(nullable): The #GstUri to modify.
2595  * @query_table: (transfer none)(nullable)(element-type gchar* gchar*): The new
2596  *               query table to use.
2597  *
2598  * Set the query table to use in the URI. The old table is unreferenced and a
2599  * reference to the new one is used instead. A value if %NULL for @query_table
2600  * will remove the query string from the URI.
2601  *
2602  * Returns: %TRUE if the new table was sucessfully used for the query table.
2603  *
2604  * Since: 1.6
2605  */
2606 gboolean
2607 gst_uri_set_query_table (GstUri * uri, GHashTable * query_table)
2608 {
2609   GHashTable *old_table = NULL;
2610
2611   if (!uri)
2612     return query_table == NULL;
2613   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2614
2615   old_table = uri->query;
2616   if (query_table)
2617     uri->query = g_hash_table_ref (query_table);
2618   else
2619     uri->query = NULL;
2620   if (old_table)
2621     g_hash_table_unref (old_table);
2622
2623   return TRUE;
2624 }
2625
2626 /**
2627  * gst_uri_set_query_value:
2628  * @uri: (transfer none)(nullable): The #GstUri to modify.
2629  * @query_key: (transfer none): The key for the query entry.
2630  * @query_value: (transfer none)(nullable): The value for the key.
2631  *
2632  * This inserts or replaces a key in the query table. A @query_value of %NULL
2633  * indicates that the key has no associated value, but will still be present in
2634  * the query string.
2635  *
2636  * Returns: %TRUE if the query table was sucessfully updated.
2637  *
2638  * Since: 1.6
2639  */
2640 gboolean
2641 gst_uri_set_query_value (GstUri * uri, const gchar * query_key,
2642     const gchar * query_value)
2643 {
2644   if (!uri)
2645     return FALSE;
2646   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2647
2648   if (!uri->query) {
2649     uri->query = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
2650         g_free);
2651   }
2652   g_hash_table_insert (uri->query, g_strdup (query_key),
2653       g_strdup (query_value));
2654
2655   return TRUE;
2656 }
2657
2658 /**
2659  * gst_uri_remove_query_key:
2660  * @uri: (transfer none)(nullable): The #GstUri to modify.
2661  * @query_key: The key to remove.
2662  *
2663  * Remove an entry from the query table by key.
2664  *
2665  * Returns: %TRUE if the key existed in the table and was removed.
2666  *
2667  * Since: 1.6
2668  */
2669 gboolean
2670 gst_uri_remove_query_key (GstUri * uri, const gchar * query_key)
2671 {
2672   gboolean result;
2673
2674   if (!uri)
2675     return FALSE;
2676   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2677   if (!uri->query)
2678     return FALSE;
2679
2680   result = g_hash_table_remove (uri->query, query_key);
2681   /* if this was the last query entry, remove the query string completely */
2682   if (result && g_hash_table_size (uri->query) == 0) {
2683     g_hash_table_unref (uri->query);
2684     uri->query = NULL;
2685   }
2686   return result;
2687 }
2688
2689 /**
2690  * gst_uri_query_has_key:
2691  * @uri: (nullable): The #GstUri to examine.
2692  * @query_key: The key to lookup.
2693  *
2694  * Check if there is a query table entry for the @query_key key.
2695  *
2696  * Returns: %TRUE if @query_key exists in the URI query table.
2697  *
2698  * Since: 1.6
2699  */
2700 gboolean
2701 gst_uri_query_has_key (const GstUri * uri, const gchar * query_key)
2702 {
2703   if (!uri)
2704     return FALSE;
2705   g_return_val_if_fail (GST_IS_URI (uri), FALSE);
2706   if (!uri->query)
2707     return FALSE;
2708
2709   return g_hash_table_contains (uri->query, query_key);
2710 }
2711
2712 /**
2713  * gst_uri_get_query_value:
2714  * @uri: (nullable): The #GstUri to examine.
2715  * @query_key: The key to lookup.
2716  *
2717  * Get the value associated with the @query_key key. Will return %NULL if the
2718  * key has no value or if the key does not exist in the URI query table. Because
2719  * %NULL is returned for both missing keys and keys with no value, you should
2720  * use gst_uri_query_has_key() to determine if a key is present in the URI
2721  * query.
2722  *
2723  * Returns: The value for the given key, or %NULL if not found.
2724  *
2725  * Since: 1.6
2726  */
2727 const gchar *
2728 gst_uri_get_query_value (const GstUri * uri, const gchar * query_key)
2729 {
2730   if (!uri)
2731     return NULL;
2732   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2733   if (!uri->query)
2734     return NULL;
2735
2736   return g_hash_table_lookup (uri->query, query_key);
2737 }
2738
2739 /**
2740  * gst_uri_get_query_keys:
2741  * @uri: (nullable): The #GstUri to examine.
2742  *
2743  * Get a list of the query keys from the URI.
2744  *
2745  * Returns: (transfer container)(element-type gchar*): A list of keys from
2746  *          the URI query. Free the list with g_list_free().
2747  *
2748  * Since: 1.6
2749  */
2750 GList *
2751 gst_uri_get_query_keys (const GstUri * uri)
2752 {
2753   if (!uri)
2754     return NULL;
2755   g_return_val_if_fail (GST_IS_URI (uri), NULL);
2756   if (!uri->query)
2757     return NULL;
2758
2759   return g_hash_table_get_keys (uri->query);
2760 }
2761
2762 /**
2763  * gst_uri_get_fragment:
2764  * @uri: (nullable): This #GstUri object.
2765  *
2766  * Get the fragment name from the URI or %NULL if it doesn't exist.
2767  * If @uri is %NULL then returns %NULL.
2768  *
2769  * Returns: The host name from the #GstUri object or %NULL.
2770  *
2771  * Since: 1.6
2772  */
2773 const gchar *
2774 gst_uri_get_fragment (const GstUri * uri)
2775 {
2776   g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL);
2777   return (uri ? uri->fragment : NULL);
2778 }
2779
2780 /**
2781  * gst_uri_set_fragment:
2782  * @uri: (transfer none)(nullable): The #GstUri to modify.
2783  * @fragment: (nullable): The fragment string to set.
2784  *
2785  * Sets the fragment string in the URI. Use a value of %NULL in @fragment to
2786  * unset the fragment string.
2787  *
2788  * Returns: %TRUE if the fragment was set/unset successfully.
2789  *
2790  * Since: 1.6
2791  */
2792 gboolean
2793 gst_uri_set_fragment (GstUri * uri, const gchar * fragment)
2794 {
2795   if (!uri)
2796     return fragment == NULL;
2797   g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE);
2798
2799   g_free (uri->fragment);
2800   uri->fragment = g_strdup (fragment);
2801   return TRUE;
2802 }