docs: convert NULL, TRUE, and FALSE to %NULL, %TRUE, and %FALSE
[platform/upstream/gstreamer.git] / gst / gsturi.c
1 /* GStreamer
2  * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3  *                    2000 Wim Taymans <wtay@chello.be>
4  * Copyright (C) 2011 Tim-Philipp Müller <tim centricular net>
5  *
6  * gsturi.c: register URI handlers
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 /**
25  * SECTION:gsturihandler
26  * @short_description: Interface to ease URI handling in plugins.
27  *
28  * The URIHandler is an interface that is implemented by Source and Sink
29  * #GstElement to simplify then handling of URI.
30  *
31  * An application can use the following functions to quickly get an element
32  * that handles the given URI for reading or writing
33  * (gst_element_make_from_uri()).
34  *
35  * Source and Sink plugins should implement this interface when possible.
36  */
37
38 #ifdef HAVE_CONFIG_H
39 #  include "config.h"
40 #endif
41
42 #include "gst_private.h"
43 #include "gst.h"
44 #include "gsturi.h"
45 #include "gstinfo.h"
46 #include "gstregistry.h"
47
48 #include "gst-i18n-lib.h"
49
50 #include <string.h>
51
52 GST_DEBUG_CATEGORY_STATIC (gst_uri_handler_debug);
53 #define GST_CAT_DEFAULT gst_uri_handler_debug
54
55 GType
56 gst_uri_handler_get_type (void)
57 {
58   static volatile gsize urihandler_type = 0;
59
60   if (g_once_init_enter (&urihandler_type)) {
61     GType _type;
62     static const GTypeInfo urihandler_info = {
63       sizeof (GstURIHandlerInterface),
64       NULL,
65       NULL,
66       NULL,
67       NULL,
68       NULL,
69       0,
70       0,
71       NULL,
72       NULL
73     };
74
75     _type = g_type_register_static (G_TYPE_INTERFACE,
76         "GstURIHandler", &urihandler_info, 0);
77
78     GST_DEBUG_CATEGORY_INIT (gst_uri_handler_debug, "GST_URI", GST_DEBUG_BOLD,
79         "handling of URIs");
80     g_once_init_leave (&urihandler_type, _type);
81   }
82   return urihandler_type;
83 }
84
85 GQuark
86 gst_uri_error_quark (void)
87 {
88   return g_quark_from_static_string ("gst-uri-error-quark");
89 }
90
91 static const guchar acceptable[96] = {  /* X0   X1   X2   X3   X4   X5   X6   X7   X8   X9   XA   XB   XC   XD   XE   XF */
92   0x00, 0x3F, 0x20, 0x20, 0x20, 0x00, 0x2C, 0x3F, 0x3F, 0x3F, 0x3F, 0x22, 0x20, 0x3F, 0x3F, 0x1C,       /* 2X  !"#$%&'()*+,-./   */
93   0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x38, 0x20, 0x20, 0x2C, 0x20, 0x2C,       /* 3X 0123456789:;<=>?   */
94   0x30, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,       /* 4X @ABCDEFGHIJKLMNO   */
95   0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x3F,       /* 5X PQRSTUVWXYZ[\]^_   */
96   0x20, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,       /* 6X `abcdefghijklmno   */
97   0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x3F, 0x20        /* 7X pqrstuvwxyz{|}~DEL */
98 };
99
100 typedef enum
101 {
102   UNSAFE_ALL = 0x1,             /* Escape all unsafe characters   */
103   UNSAFE_ALLOW_PLUS = 0x2,      /* Allows '+'  */
104   UNSAFE_PATH = 0x4,            /* Allows '/' and '?' and '&' and '='  */
105   UNSAFE_DOS_PATH = 0x8,        /* Allows '/' and '?' and '&' and '=' and ':' */
106   UNSAFE_HOST = 0x10,           /* Allows '/' and ':' and '@' */
107   UNSAFE_SLASHES = 0x20         /* Allows all characters except for '/' and '%' */
108 } UnsafeCharacterSet;
109
110 #define HEX_ESCAPE '%'
111
112 /*  Escape undesirable characters using %
113  *  -------------------------------------
114  *
115  * This function takes a pointer to a string in which
116  * some characters may be unacceptable unescaped.
117  * It returns a string which has these characters
118  * represented by a '%' character followed by two hex digits.
119  *
120  * This routine returns a g_malloced string.
121  */
122
123 static const gchar hex[16] = "0123456789ABCDEF";
124
125 static gchar *
126 escape_string_internal (const gchar * string, UnsafeCharacterSet mask)
127 {
128 #define ACCEPTABLE_CHAR(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask))
129
130   const gchar *p;
131   gchar *q;
132   gchar *result;
133   guchar c;
134   gint unacceptable;
135   UnsafeCharacterSet use_mask;
136
137   g_return_val_if_fail (mask == UNSAFE_ALL
138       || mask == UNSAFE_ALLOW_PLUS
139       || mask == UNSAFE_PATH
140       || mask == UNSAFE_DOS_PATH
141       || mask == UNSAFE_HOST || mask == UNSAFE_SLASHES, NULL);
142
143   if (string == NULL) {
144     return NULL;
145   }
146
147   unacceptable = 0;
148   use_mask = mask;
149   for (p = string; *p != '\0'; p++) {
150     c = *p;
151     if (!ACCEPTABLE_CHAR (c)) {
152       unacceptable++;
153     }
154     if ((use_mask == UNSAFE_HOST) && (unacceptable || (c == '/'))) {
155       /* when escaping a host, if we hit something that needs to be escaped, or we finally
156        * hit a path separator, revert to path mode (the host segment of the url is over).
157        */
158       use_mask = UNSAFE_PATH;
159     }
160   }
161
162   result = g_malloc (p - string + unacceptable * 2 + 1);
163
164   use_mask = mask;
165   for (q = result, p = string; *p != '\0'; p++) {
166     c = *p;
167
168     if (!ACCEPTABLE_CHAR (c)) {
169       *q++ = HEX_ESCAPE;        /* means hex coming */
170       *q++ = hex[c >> 4];
171       *q++ = hex[c & 15];
172     } else {
173       *q++ = c;
174     }
175     if ((use_mask == UNSAFE_HOST) && (!ACCEPTABLE_CHAR (c) || (c == '/'))) {
176       use_mask = UNSAFE_PATH;
177     }
178   }
179
180   *q = '\0';
181
182   return result;
183 }
184
185 /* escape_string:
186  * @string: string to be escaped
187  *
188  * Escapes @string, replacing any and all special characters
189  * with equivalent escape sequences.
190  *
191  * Return value: a newly allocated string equivalent to @string
192  * but with all special characters escaped
193  **/
194 static gchar *
195 escape_string (const gchar * string)
196 {
197   return escape_string_internal (string, UNSAFE_ALL);
198 }
199
200 static int
201 hex_to_int (gchar c)
202 {
203   return c >= '0' && c <= '9' ? c - '0'
204       : c >= 'A' && c <= 'F' ? c - 'A' + 10
205       : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
206 }
207
208 static int
209 unescape_character (const char *scanner)
210 {
211   int first_digit;
212   int second_digit;
213
214   first_digit = hex_to_int (*scanner++);
215   if (first_digit < 0) {
216     return -1;
217   }
218
219   second_digit = hex_to_int (*scanner);
220   if (second_digit < 0) {
221     return -1;
222   }
223
224   return (first_digit << 4) | second_digit;
225 }
226
227 /* unescape_string:
228  * @escaped_string: an escaped URI, path, or other string
229  * @illegal_characters: a string containing a sequence of characters
230  * considered "illegal", '\0' is automatically in this list.
231  *
232  * Decodes escaped characters (i.e. PERCENTxx sequences) in @escaped_string.
233  * Characters are encoded in PERCENTxy form, where xy is the ASCII hex code
234  * for character 16x+y.
235  *
236  * Return value: a newly allocated string with the unescaped equivalents,
237  * or %NULL if @escaped_string contained one of the characters
238  * in @illegal_characters.
239  **/
240 static char *
241 unescape_string (const gchar * escaped_string, const gchar * illegal_characters)
242 {
243   const gchar *in;
244   gchar *out, *result;
245   gint character;
246
247   if (escaped_string == NULL) {
248     return NULL;
249   }
250
251   result = g_malloc (strlen (escaped_string) + 1);
252
253   out = result;
254   for (in = escaped_string; *in != '\0'; in++) {
255     character = *in;
256     if (*in == HEX_ESCAPE) {
257       character = unescape_character (in + 1);
258
259       /* Check for an illegal character. We consider '\0' illegal here. */
260       if (character <= 0
261           || (illegal_characters != NULL
262               && strchr (illegal_characters, (char) character) != NULL)) {
263         g_free (result);
264         return NULL;
265       }
266       in += 2;
267     }
268     *out++ = (char) character;
269   }
270
271   *out = '\0';
272   g_assert ((gsize) (out - result) <= strlen (escaped_string));
273   return result;
274
275 }
276
277
278 static void
279 gst_uri_protocol_check_internal (const gchar * uri, gchar ** endptr)
280 {
281   gchar *check = (gchar *) uri;
282
283   g_assert (uri != NULL);
284   g_assert (endptr != NULL);
285
286   if (g_ascii_isalpha (*check)) {
287     check++;
288     while (g_ascii_isalnum (*check) || *check == '+'
289         || *check == '-' || *check == '.')
290       check++;
291   }
292
293   *endptr = check;
294 }
295
296 /**
297  * gst_uri_protocol_is_valid:
298  * @protocol: A string
299  *
300  * Tests if the given string is a valid protocol identifier. Protocols
301  * must consist of alphanumeric characters, '+', '-' and '.' and must
302  * start with a alphabetic character. See RFC 3986 Section 3.1.
303  *
304  * Returns: %TRUE if the string is a valid protocol identifier, %FALSE otherwise.
305  */
306 gboolean
307 gst_uri_protocol_is_valid (const gchar * protocol)
308 {
309   gchar *endptr;
310
311   g_return_val_if_fail (protocol != NULL, FALSE);
312
313   gst_uri_protocol_check_internal (protocol, &endptr);
314
315   return *endptr == '\0' && ((gsize) (endptr - protocol)) >= 2;
316 }
317
318 /**
319  * gst_uri_is_valid:
320  * @uri: A URI string
321  *
322  * Tests if the given string is a valid URI identifier. URIs start with a valid
323  * scheme followed by ":" and maybe a string identifying the location.
324  *
325  * Returns: %TRUE if the string is a valid URI
326  */
327 gboolean
328 gst_uri_is_valid (const gchar * uri)
329 {
330   gchar *endptr;
331
332   g_return_val_if_fail (uri != NULL, FALSE);
333
334   gst_uri_protocol_check_internal (uri, &endptr);
335
336   return *endptr == ':' && ((gsize) (endptr - uri)) >= 2;
337 }
338
339 /**
340  * gst_uri_get_protocol:
341  * @uri: A URI string
342  *
343  * Extracts the protocol out of a given valid URI. The returned string must be
344  * freed using g_free().
345  *
346  * Returns: The protocol for this URI.
347  */
348 gchar *
349 gst_uri_get_protocol (const gchar * uri)
350 {
351   gchar *colon;
352
353   g_return_val_if_fail (uri != NULL, NULL);
354   g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
355
356   colon = strstr (uri, ":");
357
358   return g_ascii_strdown (uri, colon - uri);
359 }
360
361 /**
362  * gst_uri_has_protocol:
363  * @uri: a URI string
364  * @protocol: a protocol string (e.g. "http")
365  *
366  * Checks if the protocol of a given valid URI matches @protocol.
367  *
368  * Returns: %TRUE if the protocol matches.
369  */
370 gboolean
371 gst_uri_has_protocol (const gchar * uri, const gchar * protocol)
372 {
373   gchar *colon;
374
375   g_return_val_if_fail (uri != NULL, FALSE);
376   g_return_val_if_fail (protocol != NULL, FALSE);
377   g_return_val_if_fail (gst_uri_is_valid (uri), FALSE);
378
379   colon = strstr (uri, ":");
380
381   if (colon == NULL)
382     return FALSE;
383
384   return (g_ascii_strncasecmp (uri, protocol, (gsize) (colon - uri)) == 0);
385 }
386
387 /**
388  * gst_uri_get_location:
389  * @uri: A URI string
390  *
391  * Extracts the location out of a given valid URI, ie. the protocol and "://"
392  * are stripped from the URI, which means that the location returned includes
393  * the hostname if one is specified. The returned string must be freed using
394  * g_free().
395  *
396  * Free-function: g_free
397  *
398  * Returns: (transfer full): the location for this URI. Returns %NULL if the
399  *     URI isn't valid. If the URI does not contain a location, an empty
400  *     string is returned.
401  */
402 gchar *
403 gst_uri_get_location (const gchar * uri)
404 {
405   const gchar *colon;
406   gchar *unescaped = NULL;
407
408   g_return_val_if_fail (uri != NULL, NULL);
409   g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
410
411   colon = strstr (uri, "://");
412   if (!colon)
413     return NULL;
414
415   unescaped = unescape_string (colon + 3, "/");
416
417   /* On Windows an URI might look like file:///c:/foo/bar.txt or
418    * file:///c|/foo/bar.txt (some Netscape versions) and we want to
419    * return c:/foo/bar.txt as location rather than /c:/foo/bar.txt.
420    * Can't use g_filename_from_uri() here because it will only handle the
421    * file:// protocol */
422 #ifdef G_OS_WIN32
423   if (unescaped != NULL && unescaped[0] == '/' &&
424       g_ascii_isalpha (unescaped[1]) &&
425       (unescaped[2] == ':' || unescaped[2] == '|')) {
426     unescaped[2] = ':';
427     memmove (unescaped, unescaped + 1, strlen (unescaped + 1) + 1);
428   }
429 #endif
430
431   GST_LOG ("extracted location '%s' from URI '%s'", GST_STR_NULL (unescaped),
432       uri);
433   return unescaped;
434 }
435
436 /**
437  * gst_uri_construct:
438  * @protocol: Protocol for URI
439  * @location: (transfer none): Location for URI
440  *
441  * Constructs a URI for a given valid protocol and location.
442  *
443  * Free-function: g_free
444  *
445  * Returns: (transfer full): a new string for this URI. Returns %NULL if the
446  *     given URI protocol is not valid, or the given location is %NULL.
447  */
448 gchar *
449 gst_uri_construct (const gchar * protocol, const gchar * location)
450 {
451   char *escaped, *proto_lowercase;
452   char *retval;
453
454   g_return_val_if_fail (gst_uri_protocol_is_valid (protocol), NULL);
455   g_return_val_if_fail (location != NULL, NULL);
456
457   proto_lowercase = g_ascii_strdown (protocol, -1);
458   escaped = escape_string (location);
459   retval = g_strdup_printf ("%s://%s", proto_lowercase, escaped);
460   g_free (escaped);
461   g_free (proto_lowercase);
462
463   return retval;
464 }
465
466 typedef struct
467 {
468   GstURIType type;
469   const gchar *protocol;
470 }
471 SearchEntry;
472
473 static gboolean
474 search_by_entry (GstPluginFeature * feature, gpointer search_entry)
475 {
476   const gchar *const *protocols;
477   GstElementFactory *factory;
478   SearchEntry *entry = (SearchEntry *) search_entry;
479
480   if (!GST_IS_ELEMENT_FACTORY (feature))
481     return FALSE;
482   factory = GST_ELEMENT_FACTORY_CAST (feature);
483
484   if (factory->uri_type != entry->type)
485     return FALSE;
486
487   protocols = gst_element_factory_get_uri_protocols (factory);
488
489   if (protocols == NULL) {
490     g_warning ("Factory '%s' implements GstUriHandler interface but returned "
491         "no supported protocols!", gst_plugin_feature_get_name (feature));
492     return FALSE;
493   }
494
495   while (*protocols != NULL) {
496     if (g_ascii_strcasecmp (*protocols, entry->protocol) == 0)
497       return TRUE;
498     protocols++;
499   }
500   return FALSE;
501 }
502
503 static gint
504 sort_by_rank (GstPluginFeature * first, GstPluginFeature * second)
505 {
506   return gst_plugin_feature_get_rank (second) -
507       gst_plugin_feature_get_rank (first);
508 }
509
510 static GList *
511 get_element_factories_from_uri_protocol (const GstURIType type,
512     const gchar * protocol)
513 {
514   GList *possibilities;
515   SearchEntry entry;
516
517   g_return_val_if_fail (protocol, NULL);
518
519   entry.type = type;
520   entry.protocol = protocol;
521   possibilities = gst_registry_feature_filter (gst_registry_get (),
522       search_by_entry, FALSE, &entry);
523
524   return possibilities;
525 }
526
527 /**
528  * gst_uri_protocol_is_supported:
529  * @type: Whether to check for a source or a sink
530  * @protocol: Protocol that should be checked for (e.g. "http" or "smb")
531  *
532  * Checks if an element exists that supports the given URI protocol. Note
533  * that a positive return value does not imply that a subsequent call to
534  * gst_element_make_from_uri() is guaranteed to work.
535  *
536  * Returns: %TRUE
537 */
538 gboolean
539 gst_uri_protocol_is_supported (const GstURIType type, const gchar * protocol)
540 {
541   GList *possibilities;
542
543   g_return_val_if_fail (protocol, FALSE);
544
545   possibilities = get_element_factories_from_uri_protocol (type, protocol);
546
547   if (possibilities) {
548     g_list_free (possibilities);
549     return TRUE;
550   } else
551     return FALSE;
552 }
553
554 /**
555  * gst_element_make_from_uri:
556  * @type: Whether to create a source or a sink
557  * @uri: URI to create an element for
558  * @elementname: (allow-none): Name of created element, can be %NULL.
559  * @error: (allow-none): address where to store error information, or %NULL.
560  *
561  * Creates an element for handling the given URI.
562  *
563  * Returns: (transfer floating): a new element or %NULL if none could be created
564  */
565 GstElement *
566 gst_element_make_from_uri (const GstURIType type, const gchar * uri,
567     const gchar * elementname, GError ** error)
568 {
569   GList *possibilities, *walk;
570   gchar *protocol;
571   GstElement *ret = NULL;
572
573   g_return_val_if_fail (gst_is_initialized (), NULL);
574   g_return_val_if_fail (GST_URI_TYPE_IS_VALID (type), NULL);
575   g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
576   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
577
578   GST_DEBUG ("type:%d, uri:%s, elementname:%s", type, uri, elementname);
579
580   protocol = gst_uri_get_protocol (uri);
581   possibilities = get_element_factories_from_uri_protocol (type, protocol);
582
583   if (!possibilities) {
584     GST_DEBUG ("No %s for URI '%s'", type == GST_URI_SINK ? "sink" : "source",
585         uri);
586     /* The error message isn't great, but we don't expect applications to
587      * show that error to users, but call the missing plugins functions */
588     g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
589         _("No URI handler for the %s protocol found"), protocol);
590     g_free (protocol);
591     return NULL;
592   }
593   g_free (protocol);
594
595   possibilities = g_list_sort (possibilities, (GCompareFunc) sort_by_rank);
596   walk = possibilities;
597   while (walk) {
598     GstElementFactory *factory = walk->data;
599     GError *uri_err = NULL;
600
601     ret = gst_element_factory_create (factory, elementname);
602     if (ret != NULL) {
603       GstURIHandler *handler = GST_URI_HANDLER (ret);
604
605       if (gst_uri_handler_set_uri (handler, uri, &uri_err))
606         break;
607
608       GST_WARNING ("%s didn't accept URI '%s': %s", GST_OBJECT_NAME (ret), uri,
609           uri_err->message);
610
611       if (error != NULL && *error == NULL)
612         g_propagate_error (error, uri_err);
613       else
614         g_error_free (uri_err);
615
616       gst_object_unref (ret);
617       ret = NULL;
618     }
619     walk = walk->next;
620   }
621   gst_plugin_feature_list_free (possibilities);
622
623   GST_LOG_OBJECT (ret, "created %s for URL '%s'",
624       type == GST_URI_SINK ? "sink" : "source", uri);
625
626   /* if the first handler didn't work, but we found another one that works */
627   if (ret != NULL)
628     g_clear_error (error);
629
630   return ret;
631 }
632
633 /**
634  * gst_uri_handler_get_uri_type:
635  * @handler: A #GstURIHandler.
636  *
637  * Gets the type of the given URI handler
638  *
639  * Returns: the #GstURIType of the URI handler.
640  * Returns #GST_URI_UNKNOWN if the @handler isn't implemented correctly.
641  */
642 guint
643 gst_uri_handler_get_uri_type (GstURIHandler * handler)
644 {
645   GstURIHandlerInterface *iface;
646   guint ret;
647
648   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), GST_URI_UNKNOWN);
649
650   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
651   g_return_val_if_fail (iface != NULL, GST_URI_UNKNOWN);
652   g_return_val_if_fail (iface->get_type != NULL, GST_URI_UNKNOWN);
653
654   ret = iface->get_type (G_OBJECT_TYPE (handler));
655   g_return_val_if_fail (GST_URI_TYPE_IS_VALID (ret), GST_URI_UNKNOWN);
656
657   return ret;
658 }
659
660 /**
661  * gst_uri_handler_get_protocols:
662  * @handler: A #GstURIHandler.
663  *
664  * Gets the list of protocols supported by @handler. This list may not be
665  * modified.
666  *
667  * Returns: (transfer none) (element-type utf8): the supported protocols.
668  *     Returns %NULL if the @handler isn't implemented properly, or the @handler
669  *     doesn't support any protocols.
670  */
671 const gchar *const *
672 gst_uri_handler_get_protocols (GstURIHandler * handler)
673 {
674   GstURIHandlerInterface *iface;
675   const gchar *const *ret;
676
677   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
678
679   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
680   g_return_val_if_fail (iface != NULL, NULL);
681   g_return_val_if_fail (iface->get_protocols != NULL, NULL);
682
683   ret = iface->get_protocols (G_OBJECT_TYPE (handler));
684   g_return_val_if_fail (ret != NULL, NULL);
685
686   return ret;
687 }
688
689 /**
690  * gst_uri_handler_get_uri:
691  * @handler: A #GstURIHandler
692  *
693  * Gets the currently handled URI.
694  *
695  * Returns: (transfer full): the URI currently handled by the @handler.
696  *   Returns %NULL if there are no URI currently handled. The
697  *   returned string must be freed with g_free() when no longer needed.
698  */
699 gchar *
700 gst_uri_handler_get_uri (GstURIHandler * handler)
701 {
702   GstURIHandlerInterface *iface;
703   gchar *ret;
704
705   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
706
707   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
708   g_return_val_if_fail (iface != NULL, NULL);
709   g_return_val_if_fail (iface->get_uri != NULL, NULL);
710   ret = iface->get_uri (handler);
711   if (ret != NULL)
712     g_return_val_if_fail (gst_uri_is_valid (ret), NULL);
713
714   return ret;
715 }
716
717 /**
718  * gst_uri_handler_set_uri:
719  * @handler: A #GstURIHandler
720  * @uri: URI to set
721  * @error: (allow-none): address where to store a #GError in case of
722  *    an error, or %NULL
723  *
724  * Tries to set the URI of the given handler.
725  *
726  * Returns: %TRUE if the URI was set successfully, else %FALSE.
727  */
728 gboolean
729 gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri,
730     GError ** error)
731 {
732   GstURIHandlerInterface *iface;
733   gboolean ret;
734   gchar *new_uri, *protocol, *location, *colon;
735
736   g_return_val_if_fail (GST_IS_URI_HANDLER (handler), FALSE);
737   g_return_val_if_fail (gst_uri_is_valid (uri), FALSE);
738   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
739
740   iface = GST_URI_HANDLER_GET_INTERFACE (handler);
741   g_return_val_if_fail (iface != NULL, FALSE);
742   g_return_val_if_fail (iface->set_uri != NULL, FALSE);
743
744   protocol = gst_uri_get_protocol (uri);
745
746   if (iface->get_protocols) {
747     const gchar *const *protocols;
748     const gchar *const *p;
749     gboolean found_protocol = FALSE;
750
751     protocols = iface->get_protocols (G_OBJECT_TYPE (handler));
752     if (protocols != NULL) {
753       for (p = protocols; *p != NULL; ++p) {
754         if (g_ascii_strcasecmp (protocol, *p) == 0) {
755           found_protocol = TRUE;
756           break;
757         }
758       }
759
760       if (!found_protocol) {
761         g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
762             _("URI scheme '%s' not supported"), protocol);
763         g_free (protocol);
764         return FALSE;
765       }
766     }
767   }
768
769   colon = strstr (uri, ":");
770   location = g_strdup (colon);
771
772   new_uri = g_strdup_printf ("%s%s", protocol, location);
773
774   ret = iface->set_uri (handler, uri, error);
775
776   g_free (new_uri);
777   g_free (location);
778   g_free (protocol);
779
780   return ret;
781 }
782
783 static gchar *
784 gst_file_utils_canonicalise_path (const gchar * path)
785 {
786   gchar **parts, **p, *clean_path;
787
788 #ifdef G_OS_WIN32
789   {
790     GST_WARNING ("FIXME: canonicalise win32 path");
791     return g_strdup (path);
792   }
793 #endif
794
795   parts = g_strsplit (path, "/", -1);
796
797   p = parts;
798   while (*p != NULL) {
799     if (strcmp (*p, ".") == 0) {
800       /* just move all following parts on top of this, incl. NUL terminator */
801       g_free (*p);
802       memmove (p, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *));
803       /* re-check the new current part again in the next iteration */
804       continue;
805     } else if (strcmp (*p, "..") == 0 && p > parts) {
806       /* just move all following parts on top of the previous part, incl.
807        * NUL terminator */
808       g_free (*(p - 1));
809       g_free (*p);
810       memmove (p - 1, p + 1, (g_strv_length (p + 1) + 1) * sizeof (gchar *));
811       /* re-check the new current part again in the next iteration */
812       --p;
813       continue;
814     }
815     ++p;
816   }
817   if (*path == '/') {
818     guint num_parts;
819
820     num_parts = g_strv_length (parts) + 1;      /* incl. terminator */
821     parts = g_renew (gchar *, parts, num_parts + 1);
822     memmove (parts + 1, parts, num_parts * sizeof (gchar *));
823     parts[0] = g_strdup ("/");
824   }
825
826   clean_path = g_build_filenamev (parts);
827   g_strfreev (parts);
828   return clean_path;
829 }
830
831 static gboolean
832 file_path_contains_relatives (const gchar * path)
833 {
834   return (strstr (path, "/./") != NULL || strstr (path, "/../") != NULL ||
835       strstr (path, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S) != NULL ||
836       strstr (path, G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S) != NULL);
837 }
838
839 /**
840  * gst_filename_to_uri:
841  * @filename: absolute or relative file name path
842  * @error: pointer to error, or %NULL
843  *
844  * Similar to g_filename_to_uri(), but attempts to handle relative file paths
845  * as well. Before converting @filename into an URI, it will be prefixed by
846  * the current working directory if it is a relative path, and then the path
847  * will be canonicalised so that it doesn't contain any './' or '../' segments.
848  *
849  * On Windows #filename should be in UTF-8 encoding.
850  */
851 gchar *
852 gst_filename_to_uri (const gchar * filename, GError ** error)
853 {
854   gchar *abs_location = NULL;
855   gchar *uri, *abs_clean;
856
857   g_return_val_if_fail (filename != NULL, NULL);
858   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
859
860   if (g_path_is_absolute (filename)) {
861     if (!file_path_contains_relatives (filename)) {
862       uri = g_filename_to_uri (filename, NULL, error);
863       goto beach;
864     }
865
866     abs_location = g_strdup (filename);
867   } else {
868     gchar *cwd;
869
870     cwd = g_get_current_dir ();
871     abs_location = g_build_filename (cwd, filename, NULL);
872     g_free (cwd);
873
874     if (!file_path_contains_relatives (abs_location)) {
875       uri = g_filename_to_uri (abs_location, NULL, error);
876       goto beach;
877     }
878   }
879
880   /* path is now absolute, but contains '.' or '..' */
881   abs_clean = gst_file_utils_canonicalise_path (abs_location);
882   GST_LOG ("'%s' -> '%s' -> '%s'", filename, abs_location, abs_clean);
883   uri = g_filename_to_uri (abs_clean, NULL, error);
884   g_free (abs_clean);
885
886 beach:
887
888   g_free (abs_location);
889   GST_DEBUG ("'%s' -> '%s'", filename, uri);
890   return uri;
891 }