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