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