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