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