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