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