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