2 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
3 * 2000 Wim Taymans <wtay@chello.be>
5 * gsturi.c: register URI handlers
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the
19 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
24 * SECTION:gsturihandler
25 * @short_description: Interface to ease URI handling in plugins.
27 * The URIHandler is an interface that is implemented by Source and Sink
28 * #GstElement to simplify then handling of URI.
30 * An application can use the following functions to quickly get an element
31 * that handles the given URI for reading or writing
32 * (gst_element_make_from_uri()).
34 * Source and Sink plugins should implement this interface when possible.
36 * Last reviewed on 2005-11-09 (0.9.4)
43 #include "gst_private.h"
46 #include "gstmarshal.h"
47 #include "gstregistry.h"
51 GST_DEBUG_CATEGORY_STATIC (gst_uri_handler_debug);
52 #define GST_CAT_DEFAULT gst_uri_handler_debug
54 static void gst_uri_handler_base_init (gpointer g_class);
57 gst_uri_handler_get_type (void)
59 static GType urihandler_type = 0;
61 if (G_UNLIKELY (urihandler_type == 0)) {
62 static const GTypeInfo urihandler_info = {
63 sizeof (GstURIHandlerInterface),
64 gst_uri_handler_base_init,
75 urihandler_type = g_type_register_static (G_TYPE_INTERFACE,
76 "GstURIHandler", &urihandler_info, 0);
78 GST_DEBUG_CATEGORY_INIT (gst_uri_handler_debug, "GST_URI", GST_DEBUG_BOLD,
81 return urihandler_type;
84 gst_uri_handler_base_init (gpointer g_class)
86 static gboolean initialized = FALSE;
88 if (G_UNLIKELY (!initialized)) {
91 * GstURIHandler::new-uri:
92 * @handler: The #GstURIHandler which emitted the signal
93 * @uri: The new URI, or NULL if the URI was removed
95 * The URI of the given @handler has changed.
98 g_signal_new ("new-uri", GST_TYPE_URI_HANDLER, G_SIGNAL_RUN_LAST,
99 G_STRUCT_OFFSET (GstURIHandlerInterface, new_uri), NULL, NULL,
100 gst_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
105 static const guchar acceptable[96] = { /* X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 XA XB XC XD XE XF */
106 0x00, 0x3F, 0x20, 0x20, 0x20, 0x00, 0x2C, 0x3F, 0x3F, 0x3F, 0x3F, 0x22, 0x20, 0x3F, 0x3F, 0x1C, /* 2X !"#$%&'()*+,-./ */
107 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x38, 0x20, 0x20, 0x2C, 0x20, 0x2C, /* 3X 0123456789:;<=>? */
108 0x30, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 4X @ABCDEFGHIJKLMNO */
109 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x20, 0x3F, /* 5X PQRSTUVWXYZ[\]^_ */
110 0x20, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, /* 6X `abcdefghijklmno */
111 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x20, 0x20, 0x20, 0x3F, 0x20 /* 7X pqrstuvwxyz{|}~DEL */
116 UNSAFE_ALL = 0x1, /* Escape all unsafe characters */
117 UNSAFE_ALLOW_PLUS = 0x2, /* Allows '+' */
118 UNSAFE_PATH = 0x4, /* Allows '/' and '?' and '&' and '=' */
119 UNSAFE_DOS_PATH = 0x8, /* Allows '/' and '?' and '&' and '=' and ':' */
120 UNSAFE_HOST = 0x10, /* Allows '/' and ':' and '@' */
121 UNSAFE_SLASHES = 0x20 /* Allows all characters except for '/' and '%' */
122 } UnsafeCharacterSet;
124 #define HEX_ESCAPE '%'
126 /* Escape undesirable characters using %
127 * -------------------------------------
129 * This function takes a pointer to a string in which
130 * some characters may be unacceptable unescaped.
131 * It returns a string which has these characters
132 * represented by a '%' character followed by two hex digits.
134 * This routine returns a g_malloced string.
137 static const gchar hex[16] = "0123456789ABCDEF";
140 escape_string_internal (const gchar * string, UnsafeCharacterSet mask)
142 #define ACCEPTABLE_CHAR(a) ((a)>=32 && (a)<128 && (acceptable[(a)-32] & use_mask))
149 UnsafeCharacterSet use_mask;
151 g_return_val_if_fail (mask == UNSAFE_ALL
152 || mask == UNSAFE_ALLOW_PLUS
153 || mask == UNSAFE_PATH
154 || mask == UNSAFE_DOS_PATH
155 || mask == UNSAFE_HOST || mask == UNSAFE_SLASHES, NULL);
157 if (string == NULL) {
163 for (p = string; *p != '\0'; p++) {
165 if (!ACCEPTABLE_CHAR (c)) {
168 if ((use_mask == UNSAFE_HOST) && (unacceptable || (c == '/'))) {
169 /* when escaping a host, if we hit something that needs to be escaped, or we finally
170 * hit a path separator, revert to path mode (the host segment of the url is over).
172 use_mask = UNSAFE_PATH;
176 result = g_malloc (p - string + unacceptable * 2 + 1);
179 for (q = result, p = string; *p != '\0'; p++) {
182 if (!ACCEPTABLE_CHAR (c)) {
183 *q++ = HEX_ESCAPE; /* means hex coming */
189 if ((use_mask == UNSAFE_HOST) && (!ACCEPTABLE_CHAR (c) || (c == '/'))) {
190 use_mask = UNSAFE_PATH;
201 * @string: string to be escaped
203 * Escapes @string, replacing any and all special characters
204 * with equivalent escape sequences.
206 * Return value: a newly allocated string equivalent to @string
207 * but with all special characters escaped
210 escape_string (const gchar * string)
212 return escape_string_internal (string, UNSAFE_ALL);
218 return c >= '0' && c <= '9' ? c - '0'
219 : c >= 'A' && c <= 'F' ? c - 'A' + 10
220 : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
224 unescape_character (const char *scanner)
229 first_digit = hex_to_int (*scanner++);
230 if (first_digit < 0) {
234 second_digit = hex_to_int (*scanner++);
235 if (second_digit < 0) {
239 return (first_digit << 4) | second_digit;
244 * @escaped_string: an escaped URI, path, or other string
245 * @illegal_characters: a string containing a sequence of characters
246 * considered "illegal", '\0' is automatically in this list.
248 * Decodes escaped characters (i.e. PERCENTxx sequences) in @escaped_string.
249 * Characters are encoded in PERCENTxy form, where xy is the ASCII hex code
250 * for character 16x+y.
252 * Return value: a newly allocated string with the unescaped equivalents,
253 * or %NULL if @escaped_string contained one of the characters
254 * in @illegal_characters.
257 unescape_string (const gchar * escaped_string, const gchar * illegal_characters)
263 if (escaped_string == NULL) {
267 result = g_malloc (strlen (escaped_string) + 1);
270 for (in = escaped_string; *in != '\0'; in++) {
272 if (*in == HEX_ESCAPE) {
273 character = unescape_character (in + 1);
275 /* Check for an illegal character. We consider '\0' illegal here. */
277 || (illegal_characters != NULL
278 && strchr (illegal_characters, (char) character) != NULL)) {
284 *out++ = (char) character;
288 g_assert ((gsize) (out - result) <= strlen (escaped_string));
295 gst_uri_protocol_check_internal (const gchar * uri, gchar ** endptr)
297 gchar *check = (gchar *) uri;
299 g_assert (uri != NULL);
300 g_assert (endptr != NULL);
302 if (g_ascii_isalpha (*check)) {
304 while (g_ascii_isalnum (*check) || *check == '+'
305 || *check == '-' || *check == '.')
313 * gst_uri_protocol_is_valid:
314 * @protocol: A string
316 * Tests if the given string is a valid protocol identifier. Protocols
317 * must consist of alphanumeric characters, '+', '-' and '.' and must
318 * start with a alphabetic character. See RFC 3986 Section 3.1.
320 * Returns: TRUE if the string is a valid protocol identifier, FALSE otherwise.
323 gst_uri_protocol_is_valid (const gchar * protocol)
327 g_return_val_if_fail (protocol != NULL, FALSE);
329 gst_uri_protocol_check_internal (protocol, &endptr);
331 return *endptr == '\0' && endptr != protocol;
338 * Tests if the given string is a valid URI identifier. URIs start with a valid
339 * scheme followed by ":" and maybe a string identifying the location.
341 * Returns: TRUE if the string is a valid URI
344 gst_uri_is_valid (const gchar * uri)
348 g_return_val_if_fail (uri != NULL, FALSE);
350 gst_uri_protocol_check_internal (uri, &endptr);
352 return *endptr == ':';
356 * gst_uri_get_protocol:
359 * Extracts the protocol out of a given valid URI. The returned string must be
360 * freed using g_free().
362 * Returns: The protocol for this URI.
365 gst_uri_get_protocol (const gchar * uri)
369 g_return_val_if_fail (uri != NULL, NULL);
370 g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
372 colon = strstr (uri, ":");
374 return g_ascii_strdown (uri, colon - uri);
378 * gst_uri_has_protocol:
379 * @uri: an URI string
380 * @protocol: a protocol string (e.g. "http")
382 * Checks if the protocol of a given valid URI matches @protocol.
384 * Returns: %TRUE if the protocol matches.
389 gst_uri_has_protocol (const gchar * uri, const gchar * protocol)
393 g_return_val_if_fail (uri != NULL, FALSE);
394 g_return_val_if_fail (protocol != NULL, FALSE);
395 g_return_val_if_fail (gst_uri_is_valid (uri), FALSE);
397 colon = strstr (uri, ":");
402 return (g_ascii_strncasecmp (uri, protocol, (gsize) (colon - uri)) == 0);
406 * gst_uri_get_location:
409 * Extracts the location out of a given valid URI, ie. the protocol and "://"
410 * are stripped from the URI, which means that the location returned includes
411 * the hostname if one is specified. The returned string must be freed using
414 * Returns: The location for this URI. Returns NULL if the URI isn't valid. If
415 * the URI does not contain a location, an empty string is returned.
418 gst_uri_get_location (const gchar * uri)
421 gchar *unescaped = NULL;
423 g_return_val_if_fail (uri != NULL, NULL);
424 g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
426 colon = strstr (uri, "://");
430 unescaped = unescape_string (colon + 3, "/");
432 /* On Windows an URI might look like file:///c:/foo/bar.txt or
433 * file:///c|/foo/bar.txt (some Netscape versions) and we want to
434 * return c:/foo/bar.txt as location rather than /c:/foo/bar.txt.
435 * Can't use g_filename_from_uri() here because it will only handle the
436 * file:// protocol */
438 if (unescaped != NULL && unescaped[0] == '/' &&
439 g_ascii_isalpha (unescaped[1]) &&
440 (unescaped[2] == ':' || unescaped[2] == '|')) {
442 g_memmove (unescaped, unescaped + 1, strlen (unescaped + 1) + 1);
446 GST_LOG ("extracted location '%s' from URI '%s'", GST_STR_NULL (unescaped),
453 * @protocol: Protocol for URI
454 * @location: Location for URI
456 * Constructs a URI for a given valid protocol and location.
458 * Returns: a new string for this URI. Returns NULL if the given URI protocol
459 * is not valid, or the given location is NULL.
462 gst_uri_construct (const gchar * protocol, const gchar * location)
464 char *escaped, *proto_lowercase;
467 g_return_val_if_fail (gst_uri_protocol_is_valid (protocol), NULL);
468 g_return_val_if_fail (location != NULL, NULL);
470 proto_lowercase = g_ascii_strdown (protocol, -1);
471 escaped = escape_string (location);
472 retval = g_strdup_printf ("%s://%s", proto_lowercase, escaped);
474 g_free (proto_lowercase);
482 const gchar *protocol;
487 search_by_entry (GstPluginFeature * feature, gpointer search_entry)
490 GstElementFactory *factory;
491 SearchEntry *entry = (SearchEntry *) search_entry;
493 if (!GST_IS_ELEMENT_FACTORY (feature))
495 factory = GST_ELEMENT_FACTORY (feature);
497 if (gst_element_factory_get_uri_type (factory) != entry->type)
500 protocols = gst_element_factory_get_uri_protocols (factory);
502 if (protocols == NULL) {
503 g_warning ("Factory '%s' implements GstUriHandler interface but returned "
504 "no supported protocols!", gst_plugin_feature_get_name (feature));
508 while (*protocols != NULL) {
509 if (g_ascii_strcasecmp (*protocols, entry->protocol) == 0)
517 sort_by_rank (gconstpointer a, gconstpointer b)
519 GstPluginFeature *first = GST_PLUGIN_FEATURE (a);
520 GstPluginFeature *second = GST_PLUGIN_FEATURE (b);
522 return gst_plugin_feature_get_rank (second) -
523 gst_plugin_feature_get_rank (first);
527 get_element_factories_from_uri_protocol (const GstURIType type,
528 const gchar * protocol)
530 GList *possibilities;
533 g_return_val_if_fail (protocol, NULL);
536 entry.protocol = protocol;
537 possibilities = gst_registry_feature_filter (gst_registry_get_default (),
538 search_by_entry, FALSE, &entry);
540 return possibilities;
544 * gst_uri_protocol_is_supported:
545 * @type: Whether to check for a source or a sink
546 * @protocol: Protocol that should be checked for (e.g. "http" or "smb")
548 * Checks if an element exists that supports the given URI protocol. Note
549 * that a positive return value does not imply that a subsequent call to
550 * gst_element_make_from_uri() is guaranteed to work.
557 gst_uri_protocol_is_supported (const GstURIType type, const gchar * protocol)
559 GList *possibilities;
561 g_return_val_if_fail (protocol, FALSE);
563 possibilities = get_element_factories_from_uri_protocol (type, protocol);
566 g_list_free (possibilities);
573 * gst_element_make_from_uri:
574 * @type: Whether to create a source or a sink
575 * @uri: URI to create an element for
576 * @elementname: Name of created element, can be NULL.
578 * Creates an element for handling the given URI.
580 * Returns: a new element or NULL if none could be created
583 gst_element_make_from_uri (const GstURIType type, const gchar * uri,
584 const gchar * elementname)
586 GList *possibilities, *walk;
588 GstElement *ret = NULL;
590 g_return_val_if_fail (GST_URI_TYPE_IS_VALID (type), NULL);
591 g_return_val_if_fail (gst_uri_is_valid (uri), NULL);
593 protocol = gst_uri_get_protocol (uri);
594 possibilities = get_element_factories_from_uri_protocol (type, protocol);
597 if (!possibilities) {
598 GST_DEBUG ("No %s for URI '%s'", type == GST_URI_SINK ? "sink" : "source",
603 possibilities = g_list_sort (possibilities, sort_by_rank);
604 walk = possibilities;
606 if ((ret = gst_element_factory_create (GST_ELEMENT_FACTORY (walk->data),
607 elementname)) != NULL) {
608 GstURIHandler *handler = GST_URI_HANDLER (ret);
610 if (gst_uri_handler_set_uri (handler, uri))
612 gst_object_unref (ret);
617 gst_plugin_feature_list_free (possibilities);
619 GST_LOG_OBJECT (ret, "created %s for URL '%s'",
620 type == GST_URI_SINK ? "sink" : "source", uri);
625 * gst_uri_handler_get_uri_type:
626 * @handler: A #GstURIHandler.
628 * Gets the type of the given URI handler
630 * Returns: the #GstURIType of the URI handler.
631 * Returns #GST_URI_UNKNOWN if the @handler isn't implemented correctly.
634 gst_uri_handler_get_uri_type (GstURIHandler * handler)
636 GstURIHandlerInterface *iface;
639 g_return_val_if_fail (GST_IS_URI_HANDLER (handler), GST_URI_UNKNOWN);
641 iface = GST_URI_HANDLER_GET_INTERFACE (handler);
642 g_return_val_if_fail (iface != NULL, GST_URI_UNKNOWN);
643 g_return_val_if_fail (iface->get_type != NULL, GST_URI_UNKNOWN);
644 ret = iface->get_type ();
645 g_return_val_if_fail (GST_URI_TYPE_IS_VALID (ret), GST_URI_UNKNOWN);
651 * gst_uri_handler_get_protocols:
652 * @handler: A #GstURIHandler.
654 * Gets the list of protocols supported by @handler. This list may not be
657 * Returns: the supported protocols.
658 * Returns NULL if the @handler isn't implemented properly, or the @handler
659 * doesn't support any protocols.
662 gst_uri_handler_get_protocols (GstURIHandler * handler)
664 GstURIHandlerInterface *iface;
667 g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
669 iface = GST_URI_HANDLER_GET_INTERFACE (handler);
670 g_return_val_if_fail (iface != NULL, NULL);
671 g_return_val_if_fail (iface->get_protocols != NULL ||
672 iface->get_protocols_full != NULL, NULL);
674 if (iface->get_protocols != NULL) {
675 ret = iface->get_protocols ();
677 ret = iface->get_protocols_full (G_OBJECT_TYPE (handler));
679 g_return_val_if_fail (ret != NULL, NULL);
685 * gst_uri_handler_get_uri:
686 * @handler: A #GstURIHandler
688 * Gets the currently handled URI.
690 * Returns: the URI currently handled by the @handler.
691 * Returns NULL if there are no URI currently handled. The returned
692 * string must not be modified or freed.
694 G_CONST_RETURN gchar *
695 gst_uri_handler_get_uri (GstURIHandler * handler)
697 GstURIHandlerInterface *iface;
700 g_return_val_if_fail (GST_IS_URI_HANDLER (handler), NULL);
702 iface = GST_URI_HANDLER_GET_INTERFACE (handler);
703 g_return_val_if_fail (iface != NULL, NULL);
704 g_return_val_if_fail (iface->get_uri != NULL, NULL);
705 ret = iface->get_uri (handler);
707 g_return_val_if_fail (gst_uri_is_valid (ret), NULL);
713 * gst_uri_handler_set_uri:
714 * @handler: A #GstURIHandler
717 * Tries to set the URI of the given handler.
719 * Returns: TRUE if the URI was set successfully, else FALSE.
722 gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri)
724 GstURIHandlerInterface *iface;
726 gchar *new_uri, *protocol, *location, *colon;
728 g_return_val_if_fail (GST_IS_URI_HANDLER (handler), FALSE);
729 g_return_val_if_fail (gst_uri_is_valid (uri), FALSE);
731 iface = GST_URI_HANDLER_GET_INTERFACE (handler);
732 g_return_val_if_fail (iface != NULL, FALSE);
733 g_return_val_if_fail (iface->set_uri != NULL, FALSE);
735 protocol = gst_uri_get_protocol (uri);
737 colon = strstr (uri, ":");
738 location = g_strdup (colon);
740 new_uri = g_strdup_printf ("%s%s", protocol, location);
742 ret = iface->set_uri (handler, uri);
752 * gst_uri_handler_new_uri:
753 * @handler: A #GstURIHandler
754 * @uri: new URI or NULL if it was unset
756 * Emits the new-uri signal for a given handler, when that handler has a new URI.
757 * This function should only be called by URI handlers themselves.
760 gst_uri_handler_new_uri (GstURIHandler * handler, const gchar * uri)
762 g_return_if_fail (GST_IS_URI_HANDLER (handler));
764 g_signal_emit_by_name (handler, "new-uri", uri);