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 "%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 "%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