From 54c5f1855cfe87189ef2c32d74ffa9b3c40ed66d Mon Sep 17 00:00:00 2001 From: David Waring Date: Thu, 31 Jul 2014 22:18:53 +0100 Subject: [PATCH] GstUri: Add GstUri miniobject to handle URIs in an RFC 3986 compliant fashion https://bugzilla.gnome.org/show_bug.cgi?id=725221 --- docs/gst/gstreamer-docs.sgml | 1 + docs/gst/gstreamer-sections.txt | 54 ++ gst/gsturi.c | 1823 ++++++++++++++++++++++++++++++++++++++- gst/gsturi.h | 161 +++- tests/check/gst/gsturi.c | 594 +++++++++++++ win32/common/libgstreamer.def | 40 + 6 files changed, 2671 insertions(+), 2 deletions(-) diff --git a/docs/gst/gstreamer-docs.sgml b/docs/gst/gstreamer-docs.sgml index 3a2c7af..7e66574 100644 --- a/docs/gst/gstreamer-docs.sgml +++ b/docs/gst/gstreamer-docs.sgml @@ -108,6 +108,7 @@ Windows. It is released under the GNU Library General Public License + diff --git a/docs/gst/gstreamer-sections.txt b/docs/gst/gstreamer-sections.txt index 593493c..818c0d1 100644 --- a/docs/gst/gstreamer-sections.txt +++ b/docs/gst/gstreamer-sections.txt @@ -3044,6 +3044,60 @@ gst_uri_handler_get_type gst_uri_type_get_type +
+gsturi +GstUri +GST_URI_CAST +GST_URI_CONST_CAST +GstUri +gst_uri_new +gst_uri_new_with_base +gst_uri_from_string +gst_uri_from_string_with_base +gst_uri_copy +gst_uri_equal +gst_uri_join +gst_uri_join_strings +gst_uri_is_writable +gst_uri_make_writable +gst_uri_to_string +gst_uri_ref +gst_uri_unref +gst_uri_is_normalized +gst_uri_normalize +gst_uri_get_scheme +gst_uri_set_scheme +gst_uri_get_userinfo +gst_uri_set_userinfo +gst_uri_get_host +gst_uri_set_host +gst_uri_get_port +gst_uri_set_port +gst_uri_get_path +gst_uri_set_path +gst_uri_get_path_string +gst_uri_set_path_string +gst_uri_get_path_segments +gst_uri_set_path_segments +gst_uri_append_path +gst_uri_append_path_segment +gst_uri_get_query_string +gst_uri_set_query_string +gst_uri_get_query_table +gst_uri_set_query_table +gst_uri_get_query_value +gst_uri_set_query_value +gst_uri_remove_query_key +gst_uri_query_has_key +gst_uri_get_query_keys +gst_uri_get_fragment +gst_uri_set_fragment + +GST_IS_URI +GST_TYPE_URI +GST_URI +gst_uri_get_type +
gstutils diff --git a/gst/gsturi.c b/gst/gsturi.c index a3ef6fd..219e6b3 100644 --- a/gst/gsturi.c +++ b/gst/gsturi.c @@ -2,8 +2,10 @@ * Copyright (C) 1999,2000 Erik Walthinsen * 2000 Wim Taymans * Copyright (C) 2011 Tim-Philipp Müller + * Copyright (C) 2014 David Waring, British Broadcasting Corporation + * * - * gsturi.c: register URI handlers + * gsturi.c: register URI handlers and IETF RFC 3986 URI manipulations. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -48,6 +50,8 @@ #include "gst-i18n-lib.h" #include +#include +#include GST_DEBUG_CATEGORY_STATIC (gst_uri_handler_debug); #define GST_CAT_DEFAULT gst_uri_handler_debug @@ -884,3 +888,1820 @@ beach: GST_DEBUG ("'%s' -> '%s'", filename, uri); return uri; } + +/**************************************************************************** + * GstUri - GstMiniObject to parse and merge URIs according to IETF RFC 3986 + ****************************************************************************/ + +/** + * SECTION:gsturi + * @short_description: URI parsing and manipulation. + * + * A #GstUri object can be used to parse and split a URI string into its + * constituant parts. Two #GstUri objects can be joined to make a new #GstUri + * using the algorithm described in RFC3986. + */ + +/* Definition for GstUri object */ +struct _GstUri +{ + /*< private > */ + GstMiniObject mini_object; + gchar *scheme; + gchar *userinfo; + gchar *host; + guint port; + GList *path; + GHashTable *query; + gchar *fragment; +}; + +GST_DEFINE_MINI_OBJECT_TYPE (GstUri, gst_uri); + +static GstUri *_gst_uri_copy (const GstUri * uri); +static void _gst_uri_free (GstUri * uri); +static GstUri *_gst_uri_new (void); +static GList *_remove_dot_segments (GList * path); + +/** private GstUri functions **/ + +static GstUri * +_gst_uri_new (void) +{ + GstUri *uri; + uri = GST_URI_CAST (g_slice_new0 (GstUri)); + + if (uri) + gst_mini_object_init (GST_MINI_OBJECT_CAST (uri), 0, gst_uri_get_type (), + (GstMiniObjectCopyFunction) _gst_uri_copy, NULL, + (GstMiniObjectFreeFunction) _gst_uri_free); + + return uri; +} + +static void +_gst_uri_free (GstUri * uri) +{ + g_return_if_fail (GST_IS_URI (uri)); + + g_free (uri->scheme); + g_free (uri->userinfo); + g_free (uri->host); + g_list_free_full (uri->path, g_free); + if (uri->query) + g_hash_table_unref (uri->query); + g_free (uri->fragment); + + g_slice_free1 (sizeof (*uri), uri); +} + +static GHashTable * +_gst_uri_copy_query_table (GHashTable * orig) +{ + GHashTable *new = NULL; + + if (orig != NULL) { + GHashTableIter iter; + gpointer key, value; + new = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_iter_init (&iter, orig); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_hash_table_insert (new, g_strdup (key), g_strdup (value)); + } + } + + return new; +} + +static GstUri * +_gst_uri_copy (const GstUri * orig_uri) +{ + GstUri *new_uri; + + g_return_val_if_fail (GST_IS_URI (orig_uri), NULL); + + new_uri = _gst_uri_new (); + + if (new_uri) { + new_uri->scheme = g_strdup (orig_uri->scheme); + new_uri->userinfo = g_strdup (orig_uri->userinfo); + new_uri->host = g_strdup (orig_uri->host); + new_uri->port = orig_uri->port; + new_uri->path = g_list_copy_deep (orig_uri->path, (GCopyFunc) g_strdup, + NULL); + new_uri->query = _gst_uri_copy_query_table (orig_uri->query); + new_uri->fragment = g_strdup (orig_uri->fragment); + } + + return new_uri; +} + +/* + * _gst_uri_compare_lists: + * + * Compare two lists for equality. This compares the two lists, item for item, + * comparing items in the same position in the two lists. If @first is + * considered less than @second the result will be negative. If @first is + * considered to be more than @second then the result will be positive. If the + * lists are considered to be equal then the result will be 0. If two lists + * have the same items, but one list is shorter than the other, then the + * shorter list is considered to be less than the longer list. + */ +static gint +_gst_uri_compare_lists (GList * first, GList * second, GCompareFunc cmp_fn) +{ + GList *itr1, *itr2; + gint result; + + for (itr1 = first, itr2 = second; + itr1 != NULL || itr2 != NULL; itr1 = itr1->next, itr2 = itr2->next) { + if (itr1 == NULL) + return -1; + if (itr2 == NULL) + return 1; + result = cmp_fn (itr1->data, itr2->data); + if (result != 0) + return result; + } + return 0; +} + +typedef enum +{ + _GST_URI_NORMALIZE_LOWERCASE = 1, + _GST_URI_NORMALIZE_UPPERCASE = 2 +} _GstUriNormalizations; + +/* + * Find the first character that hasn't been normalized according to the @flags. + */ +static gchar * +_gst_uri_first_non_normalized_char (gchar * str, guint flags) +{ + gchar *pos; + + if (str == NULL) + return NULL; + + for (pos = str; *pos; pos++) { + if ((flags & _GST_URI_NORMALIZE_UPPERCASE) && g_ascii_islower (*pos)) + return pos; + if ((flags & _GST_URI_NORMALIZE_LOWERCASE) && g_ascii_isupper (*pos)) + return pos; + } + return NULL; +} + +static gboolean +_gst_uri_normalize_lowercase (gchar * str) +{ + gchar *pos; + gboolean ret = FALSE; + + for (pos = _gst_uri_first_non_normalized_char (str, + _GST_URI_NORMALIZE_LOWERCASE); + pos != NULL; + pos = _gst_uri_first_non_normalized_char (pos + 1, + _GST_URI_NORMALIZE_LOWERCASE)) { + *pos = g_ascii_tolower (*pos); + ret = TRUE; + } + + return ret; +} + +#define _gst_uri_normalize_scheme _gst_uri_normalize_lowercase +#define _gst_uri_normalize_hostname _gst_uri_normalize_lowercase + +static gboolean +_gst_uri_normalize_path (GList ** path) +{ + GList *new_path; + + new_path = _remove_dot_segments (*path); + if (_gst_uri_compare_lists (new_path, *path, (GCompareFunc) g_strcmp0) != 0) { + g_list_free_full (*path, g_free); + *path = new_path; + return TRUE; + } + g_list_free_full (new_path, g_free); + + return FALSE; +} + +static gboolean +_gst_uri_normalize_str_noop (gchar * str) +{ + return FALSE; +} + +static gboolean +_gst_uri_normalize_table_noop (GHashTable * table) +{ + return FALSE; +} + +#define _gst_uri_normalize_userinfo _gst_uri_normalize_str_noop +#define _gst_uri_normalize_query _gst_uri_normalize_table_noop +#define _gst_uri_normalize_fragment _gst_uri_normalize_str_noop + +/** RFC 3986 functions **/ + +static GList * +_merge (GList * base, GList * path) +{ + GList *ret, *path_copy, *last; + + path_copy = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL); + /* if base is NULL make path absolute */ + if (base == NULL) { + if (path_copy != NULL && path_copy->data != NULL) { + path_copy = g_list_prepend (path_copy, NULL); + } + return path_copy; + } + + ret = g_list_copy_deep (base, (GCopyFunc) g_strdup, NULL); + last = g_list_last (ret); + ret = g_list_remove_link (ret, last); + g_list_free_full (last, g_free); + ret = g_list_concat (ret, path_copy); + + return ret; +} + +static GList * +_remove_dot_segments (GList * path) +{ + GList *out, *elem, *next; + + if (path == NULL) + return NULL; + + out = g_list_copy_deep (path, (GCopyFunc) g_strdup, NULL); + + for (elem = out; elem; elem = next) { + next = elem->next; + if (elem->data == NULL && elem != out && next != NULL) { + out = g_list_delete_link (out, elem); + } else if (g_strcmp0 (elem->data, ".") == 0) { + g_free (elem->data); + out = g_list_delete_link (out, elem); + } else if (g_strcmp0 (elem->data, "..") == 0) { + GList *prev = g_list_previous (elem); + if (prev && (prev != out || prev->data != NULL)) { + g_free (prev->data); + out = g_list_delete_link (out, prev); + } + g_free (elem->data); + out = g_list_delete_link (out, elem); + } + } + + return out; +} + +static gchar * +_gst_uri_escape_userinfo (const gchar * userinfo) +{ + return g_uri_escape_string (userinfo, + G_URI_RESERVED_CHARS_ALLOWED_IN_USERINFO, FALSE); +} + +static gchar * +_gst_uri_escape_host (const gchar * host) +{ + return g_uri_escape_string (host, + G_URI_RESERVED_CHARS_SUBCOMPONENT_DELIMITERS, FALSE); +} + +static gchar * +_gst_uri_escape_path_segment (const gchar * segment) +{ + return g_uri_escape_string (segment, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH_ELEMENT, FALSE); +} + +static gchar * +_gst_uri_escape_http_query_element (const gchar * element) +{ + gchar *ret, *c; + + ret = g_uri_escape_string (element, "!$'()*,;:@/? ", FALSE); + for (c = ret; *c; c++) + if (*c == ' ') + *c = '+'; + return ret; +} + +static gchar * +_gst_uri_escape_fragment (const gchar * fragment) +{ + return g_uri_escape_string (fragment, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH "?", FALSE); +} + +static GList * +_gst_uri_string_to_list (const gchar * str, const gchar * sep, gboolean convert, + gboolean unescape) +{ + GList *new_list = NULL; + + if (str) { + guint pct_sep_len = 0; + gchar *pct_sep; + gchar **split_str; + + if (convert && !unescape) { + pct_sep = g_strdup_printf ("%%%2.2X", (guint) (*sep)); + pct_sep_len = 3; + } + + split_str = g_strsplit (str, sep, -1); + if (split_str) { + gchar **next_elem; + for (next_elem = split_str; *next_elem; next_elem += 1) { + gchar *elem = *next_elem; + if (*elem == '\0') { + new_list = g_list_append (new_list, NULL); + } else { + if (convert && !unescape) { + gchar *next_sep; + for (next_sep = strcasestr (elem, pct_sep); next_sep; + next_sep = strcasestr (next_sep + 1, pct_sep)) { + *next_sep = *sep; + memmove (next_sep + 1, next_sep + pct_sep_len, + strlen (next_sep + pct_sep_len) + 1); + } + } + if (unescape) { + *next_elem = g_uri_unescape_string (elem, NULL); + g_free (elem); + elem = *next_elem; + } + new_list = g_list_append (new_list, g_strdup (elem)); + } + } + } + g_strfreev (split_str); + if (convert && !unescape) + g_free (pct_sep); + } + + return new_list; +} + +static GHashTable * +_gst_uri_string_to_table (const gchar * str, const gchar * part_sep, + const gchar * kv_sep, gboolean convert, gboolean unescape) +{ + GHashTable *new_table = NULL; + + if (str) { + gchar *pct_part_sep, *pct_kv_sep; + gchar **split_parts; + + new_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (convert && !unescape) { + pct_part_sep = g_strdup_printf ("%%%2.2X", (guint) (*part_sep)); + pct_kv_sep = g_strdup_printf ("%%%2.2X", (guint) (*kv_sep)); + } + + split_parts = g_strsplit (str, part_sep, -1); + if (split_parts) { + gchar **next_part; + for (next_part = split_parts; *next_part; next_part += 1) { + gchar *part = *next_part; + gchar *kv_sep_pos; + gchar *key, *value; + /* if we are converting percent encoded versions of separators then + * substitute the part separator now. */ + if (convert && !unescape) { + gchar *next_sep; + for (next_sep = strcasestr (part, pct_part_sep); next_sep; + next_sep = strcasestr (next_sep + 1, pct_part_sep)) { + *next_sep = *part_sep; + memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1); + } + } + /* find the key/value separator within the part */ + kv_sep_pos = g_strstr_len (part, -1, kv_sep); + if (kv_sep_pos == NULL) { + if (unescape) { + key = g_uri_unescape_string (part, NULL); + } else { + key = g_strdup (part); + } + value = NULL; + } else { + if (unescape) { + key = g_uri_unescape_segment (part, kv_sep_pos, NULL); + value = g_uri_unescape_string (kv_sep_pos + 1, NULL); + } else { + key = g_strndup (part, kv_sep_pos - part); + value = g_strdup (kv_sep_pos + 1); + } + } + /* if we are converting percent encoded versions of separators then + * substitute the key/value separator in both key and value now. */ + if (convert && !unescape) { + gchar *next_sep; + for (next_sep = strcasestr (key, pct_kv_sep); next_sep; + next_sep = strcasestr (next_sep + 1, pct_kv_sep)) { + *next_sep = *kv_sep; + memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1); + } + if (value) { + for (next_sep = strcasestr (value, pct_kv_sep); next_sep; + next_sep = strcasestr (next_sep + 1, pct_kv_sep)) { + *next_sep = *kv_sep; + memmove (next_sep + 1, next_sep + 3, strlen (next_sep + 3) + 1); + } + } + } + /* add value to the table */ + g_hash_table_insert (new_table, key, value); + } + } + /* tidy up */ + g_strfreev (split_parts); + if (convert && !unescape) { + g_free (pct_part_sep); + g_free (pct_kv_sep); + } + } + + return new_table; +} + + +/* + * Method definitions. + */ + +/** + * gst_uri_new: + * @scheme: (nullable): The scheme for the new URI. + * @userinfo: (nullable): The user-info for the new URI. + * @host: (nullable): The host name for the new URI. + * @port: The port number for the new URI or %GST_URI_NO_PORT. + * @path: (nullable): The path for the new URI with '/' separating path + * elements. + * @query: (nullable): The query string for the new URI with '&' separating + * query elements. Elements containing '&' characters + * should encode them as "%26". + * @fragment: (nullable): The fragment name for the new URI. + * + * Creates a new #GstUri object with the given URI parts. The path and query + * strings will be broken down into their elements. All strings should not be + * escaped except where indicated. + * + * Returns: (transfer full): A new #GstUri object. + * + * Since: 1.6 + */ +GstUri * +gst_uri_new (const gchar * scheme, const gchar * userinfo, const gchar * host, + guint port, const gchar * path, const gchar * query, const gchar * fragment) +{ + GstUri *new_uri; + + new_uri = _gst_uri_new (); + if (new_uri) { + new_uri->scheme = g_strdup (scheme); + new_uri->userinfo = g_strdup (userinfo); + new_uri->host = g_strdup (host); + new_uri->port = port; + new_uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE); + new_uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, FALSE); + new_uri->fragment = g_strdup (fragment); + } + + return new_uri; +} + +/** + * gst_uri_new_with_base: + * @base: (transfer none)(nullable): The base URI to join the new URI to. + * @scheme: (nullable): The scheme for the new URI. + * @userinfo: (nullable): The user-info for the new URI. + * @host: (nullable): The host name for the new URI. + * @port: The port number for the new URI or %GST_URI_NO_PORT. + * @path: (nullable): The path for the new URI with '/' separating path + * elements. + * @query: (nullable): The query string for the new URI with '&' separating + * query elements. Elements containing '&' characters + * should encode them as "%26". + * @fragment: (nullable): The fragment name for the new URI. + * + * Like gst_uri_new(), but joins the new URI onto a base URI. + * + * Returns: (transfer full): The new URI joined onto @base. + * + * Since: 1.6 + */ +GstUri * +gst_uri_new_with_base (GstUri * base, const gchar * scheme, + const gchar * userinfo, const gchar * host, guint port, const gchar * path, + const gchar * query, const gchar * fragment) +{ + GstUri *new_rel_uri; + GstUri *new_uri; + + g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL); + + new_rel_uri = gst_uri_new (scheme, userinfo, host, port, path, query, + fragment); + new_uri = gst_uri_join (base, new_rel_uri); + gst_uri_unref (new_rel_uri); + + return new_uri; +} + +/** + * gst_uri_from_string: + * @uri: The URI string to parse. + * + * Parses a URI string into a new #GstUri object. + * + * Returns: (transfer full): A new #GstUri object. + * + * Since: 1.6 + */ +GstUri * +gst_uri_from_string (const gchar * uri) +{ + GstUri *uri_obj; + + uri_obj = _gst_uri_new (); + + if (uri_obj && uri != NULL) { + int i = 0; + if (g_ascii_isalpha (uri[i])) { + /* find end of scheme name */ + i++; + while (g_ascii_isalnum (uri[i]) || uri[i] == '+' || uri[i] == '-' || + uri[i] == '.') + i++; + } + if (i > 0 && uri[i] == ':') { + /* get scheme */ + uri_obj->scheme = g_strndup (uri, i); + uri += i + 1; + } + if (uri[0] == '/' && uri[1] == '/') { + const gchar *eoa, *eoui, *eoh; + /* get authority [userinfo@]host[:port] */ + uri += 2; + /* find end of authority */ + eoa = strchr (uri, '/'); + if (eoa == NULL) + eoa = uri + strlen (uri); + /* find end of userinfo */ + eoui = strchr (uri, '@'); + if (eoui != NULL && eoui < eoa) { + uri_obj->userinfo = g_uri_unescape_segment (uri, eoui, NULL); + uri = eoui + 1; + } + /* find end of host */ + if (uri[0] == '[') { + eoh = strchr (uri, ']'); + if (eoh == NULL || eoh >= eoa) + eoh = eoa - 1; + } else { + eoh = strchr (uri, ':'); + if (eoh == NULL || eoh >= eoa) + eoh = eoa - 1; + else + eoh--; + } + uri_obj->host = g_uri_unescape_segment (uri, eoh + 1, NULL); + uri = eoh + 1; + if (uri < eoa) { + /* if port number is malformed, do best effort and concat string */ + if (uri[0] != ':' || strspn (uri + 1, "0123456789") != eoa - uri - 1) { + gchar *tmp = uri_obj->host; + uri_obj->host = g_malloc (strlen (uri_obj->host) + eoa - uri + 1); + g_strlcpy (g_stpcpy (uri_obj->host, tmp), uri, eoa - uri + 1); + g_free (tmp); + } else { + /* otherwise treat port as unsigned decimal number */ + uri++; + while (uri < eoa) { + uri_obj->port = uri_obj->port * 10 + g_ascii_digit_value (*uri); + uri++; + } + } + } + uri = eoa; + } + if (uri != NULL && uri[0] != '\0') { + /* get path */ + size_t len; + len = strcspn (uri, "?#"); + if (uri[len] == '\0') { + uri_obj->path = _gst_uri_string_to_list (uri, "/", FALSE, TRUE); + uri = NULL; + } else { + if (len > 0) { + gchar *path_str = g_strndup (uri, len); + uri_obj->path = _gst_uri_string_to_list (path_str, "/", FALSE, TRUE); + g_free (path_str); + } + uri += len; + } + } + if (uri != NULL && uri[0] == '?') { + /* get query */ + gchar *eoq; + eoq = strchr (++uri, '#'); + if (eoq == NULL) { + uri_obj->query = _gst_uri_string_to_table (uri, "&", "=", TRUE, TRUE); + uri = NULL; + } else { + if (eoq != uri) { + gchar *query_str = g_strndup (uri, eoq - uri); + uri_obj->query = _gst_uri_string_to_table (query_str, "&", "=", TRUE, + TRUE); + g_free (query_str); + } + uri = eoq; + } + } + if (uri != NULL && uri[0] == '#') { + uri_obj->fragment = g_uri_unescape_string (uri + 1, NULL); + } + } + + return uri_obj; +} + +/** + * gst_uri_from_string_with_base: + * @base: (transfer none)(nullable): The base URI to join the new URI with. + * @uri: The URI string to parse. + * + * Like gst_uri_from_string() but also joins with a base URI. + * + * Returns: (transfer full): A new #GstUri object. + * + * Since: 1.6 + */ +GstUri * +gst_uri_from_string_with_base (GstUri * base, const gchar * uri) +{ + GstUri *new_rel_uri; + GstUri *new_uri; + + g_return_val_if_fail (base == NULL || GST_IS_URI (base), NULL); + + new_rel_uri = gst_uri_from_string (uri); + new_uri = gst_uri_join (base, new_rel_uri); + gst_uri_unref (new_rel_uri); + + return new_uri; +} + +/** + * gst_uri_equal: + * @first: First #GstUri to compare. + * @second: Second #GstUri to compare. + * + * Compares two #GstUri objects to see if they represent the same normalized + * URI. + * + * Returns: %TRUE if the normalized versions of the two URI's would be equal. + * + * Since: 1.6 + */ +gboolean +gst_uri_equal (const GstUri * first, const GstUri * second) +{ + gchar *first_norm = NULL, *second_norm = NULL; + GList *first_norm_list = NULL, *second_norm_list = NULL; + const gchar *first_cmp, *second_cmp; + GHashTableIter table_iter; + gpointer key, value; + int result; + + g_return_val_if_fail ((first == NULL || GST_IS_URI (first)) && + (second == NULL || GST_IS_URI (second)), FALSE); + + if (first == second) + return TRUE; + + if (first == NULL || second == NULL) + return FALSE; + + if (first->port != second->port) + return FALSE; + +/* work out a version of field value (normalized or not) to compare. + * first_cmp, second_cmp will be the values to compare later. + * first_norm, second_norm will be non-NULL if normalized versions are used, + * and need to be freed later. + */ +#define GST_URI_NORMALIZED_FIELD(pos, field, norm_fn, flags) \ + pos##_cmp = pos->field; \ + if (_gst_uri_first_non_normalized_char ((gchar*)pos##_cmp, flags) != NULL) { \ + pos##_norm = g_strdup (pos##_cmp); \ + norm_fn (pos##_norm); \ + pos##_cmp = pos##_norm; \ + } + +/* compare two string values, normalizing if needed */ +#define GST_URI_NORMALIZED_CMP_STR(field, norm_fn, flags) \ + GST_URI_NORMALIZED_FIELD (first, field, norm_fn, flags) \ + GST_URI_NORMALIZED_FIELD (second, field, norm_fn, flags) \ + result = g_strcmp0 (first_cmp, second_cmp); \ + g_free (first_norm); \ + first_norm = NULL; \ + g_free (second_norm); \ + second_norm = NULL; \ + if (result != 0) return FALSE + +/* compare two string values */ +#define GST_URI_CMP_STR(field) \ + if (g_strcmp0 (first->field, second->field) != 0) return FALSE + +/* compare two GLists, normalize lists if needed before comparison */ +#define GST_URI_NORMALIZED_CMP_LIST(field, norm_fn, copy_fn, cmp_fn, free_fn) \ + first_norm_list = g_list_copy_deep (first->field, (GCopyFunc) copy_fn, NULL); \ + norm_fn (&first_norm_list); \ + second_norm_list = g_list_copy_deep (second->field, (GCopyFunc) copy_fn, NULL); \ + norm_fn (&second_norm_list); \ + result = _gst_uri_compare_lists (first_norm_list, second_norm_list, (GCompareFunc) cmp_fn); \ + g_list_free_full (first_norm_list, free_fn); \ + g_list_free_full (second_norm_list, free_fn); \ + if (result != 0) return FALSE + + GST_URI_CMP_STR (userinfo); + + GST_URI_CMP_STR (fragment); + + GST_URI_NORMALIZED_CMP_STR (scheme, _gst_uri_normalize_scheme, + _GST_URI_NORMALIZE_LOWERCASE); + + GST_URI_NORMALIZED_CMP_STR (host, _gst_uri_normalize_hostname, + _GST_URI_NORMALIZE_LOWERCASE); + + GST_URI_NORMALIZED_CMP_LIST (path, _gst_uri_normalize_path, g_strdup, + g_strcmp0, g_free); + + if (first->query == NULL && second->query != NULL) + return FALSE; + if (first->query != NULL && second->query == NULL) + return FALSE; + if (first->query != NULL) { + if (g_hash_table_size (first->query) != g_hash_table_size (second->query)) + return FALSE; + + g_hash_table_iter_init (&table_iter, first->query); + while (g_hash_table_iter_next (&table_iter, &key, &value)) { + if (!g_hash_table_contains (second->query, key)) + return FALSE; + result = g_strcmp0 (g_hash_table_lookup (second->query, key), value); + if (result != 0) + return FALSE; + } + } +#undef GST_URI_NORMALIZED_CMP_STR +#undef GST_URI_CMP_STR +#undef GST_URI_NORMALIZED_CMP_LIST +#undef GST_URI_NORMALIZED_FIELD + + return TRUE; +} + +/** + * gst_uri_join: + * @base_uri: (transfer none)(nullable): The base URI to join another to. + * @ref_uri: (transfer none)(nullable): The reference URI to join onto the + * base URI. + * + * Join a reference URI onto a base URI using the method from RFC 3986. + * If either URI is %NULL then the other URI will be returned with the ref count + * increased. + * + * Returns: (transfer full): A #GstUri which represents the base with the + * reference URI joined on. + * + * Since: 1.6 + */ +GstUri * +gst_uri_join (GstUri * base_uri, GstUri * ref_uri) +{ + const gchar *r_scheme; + GstUri *t; + + g_return_val_if_fail ((base_uri == NULL || GST_IS_URI (base_uri)) && + (ref_uri == NULL || GST_IS_URI (ref_uri)), NULL); + + if (base_uri == NULL && ref_uri == NULL) + return NULL; + if (base_uri == NULL) { + g_return_val_if_fail (GST_IS_URI (ref_uri), NULL); + return gst_uri_ref (ref_uri); + } + if (ref_uri == NULL) { + g_return_val_if_fail (GST_IS_URI (base_uri), NULL); + return gst_uri_ref (base_uri); + } + + g_return_val_if_fail (GST_IS_URI (base_uri) && GST_IS_URI (ref_uri), NULL); + + t = _gst_uri_new (); + + if (t == NULL) + return t; + + /* process according to RFC3986 */ + r_scheme = ref_uri->scheme; + if (r_scheme != NULL && g_strcmp0 (base_uri->scheme, r_scheme) == 0) { + r_scheme = NULL; + } + if (r_scheme != NULL) { + t->scheme = g_strdup (r_scheme); + t->userinfo = g_strdup (ref_uri->userinfo); + t->host = g_strdup (ref_uri->host); + t->port = ref_uri->port; + t->path = _remove_dot_segments (ref_uri->path); + t->query = _gst_uri_copy_query_table (ref_uri->query); + } else { + if (ref_uri->host != NULL) { + t->userinfo = g_strdup (ref_uri->userinfo); + t->host = g_strdup (ref_uri->host); + t->port = ref_uri->port; + t->path = _remove_dot_segments (ref_uri->path); + t->query = _gst_uri_copy_query_table (ref_uri->query); + } else { + if (ref_uri->path == NULL) { + t->path = g_list_copy_deep (base_uri->path, (GCopyFunc) g_strdup, NULL); + if (ref_uri->query != NULL) + t->query = _gst_uri_copy_query_table (ref_uri->query); + else + t->query = _gst_uri_copy_query_table (base_uri->query); + } else { + if (ref_uri->path->data == NULL) + t->path = _remove_dot_segments (ref_uri->path); + else { + GList *mrgd = _merge (base_uri->path, ref_uri->path); + t->path = _remove_dot_segments (mrgd); + g_list_free (mrgd); + } + t->query = _gst_uri_copy_query_table (ref_uri->query); + } + t->userinfo = g_strdup (base_uri->userinfo); + t->host = g_strdup (base_uri->host); + t->port = base_uri->port; + } + t->scheme = g_strdup (base_uri->scheme); + } + t->fragment = g_strdup (ref_uri->fragment); + + return t; +} + +/** + * gst_uri_join_strings: + * @base_uri: The percent-encoded base URI. + * @ref_uri: The percent-encoded reference URI to join to the @base_uri. + * + * This is a convenience function to join two URI strings and return the result. + * The returned string should be g_free()'d after use. + * + * Returns: (transfer full): A string representing the percent-encoded join of + * the two URIs. + * + * Since: 1.6 + */ +gchar * +gst_uri_join_strings (const gchar * base_uri, const gchar * ref_uri) +{ + GstUri *base, *result; + gchar *result_uri; + + base = gst_uri_from_string (base_uri); + result = gst_uri_from_string_with_base (base, ref_uri); + result_uri = gst_uri_to_string (result); + gst_uri_unref (base); + gst_uri_unref (result); + + return result_uri; +} + +/** + * gst_uri_is_writable: + * @uri: The #GstUri object to test. + * + * Check if it is safe to write to this #GstUri. + * + * Check if the refcount of @uri is exactly 1, meaning that no other + * reference exists to the #GstUri and that the #GstUri is therefore writable. + * + * Modification of a #GstUri should only be done after verifying that it is + * writable. + * + * Returns: %TRUE if it is safe to write to the object. + * + * Since: 1.6 + */ +gboolean +gst_uri_is_writable (const GstUri * uri) +{ + g_return_val_if_fail (GST_IS_URI (uri), FALSE); + return gst_mini_object_is_writable (GST_MINI_OBJECT_CAST (uri)); +} + +/** + * gst_uri_make_writable: + * @uri: (transfer full): The #GstUri object to make writable. + * + * Make the #GstUri writable. + * + * Checks if @uri is writable, and if so the original object is returned. If + * not, then a writable copy is made and returned. This gives away the + * reference to @uri and returns a reference to the new #GstUri. + * If @uri is %NULL then %NULL is returned. + * + * Returns: (transfer full): A writable version of @uri. + * + * Since: 1.6 + */ +GstUri * +gst_uri_make_writable (GstUri * uri) +{ + g_return_val_if_fail (GST_IS_URI (uri), NULL); + return + GST_URI_CAST (gst_mini_object_make_writable (GST_MINI_OBJECT_CAST (uri))); +} + +/** + * gst_uri_to_string: + * @uri: This #GstUri to convert to a string. + * + * Convert the URI to a string. + * + * Returns the URI as held in this object as a gchar* %NUL terminated string. + * The caller should g_free() the string once they are finished with it. + * The string is put together as described in RFC 3986. + * + * Returns: (transfer full): The string version of the URI. + * + * Since: 1.6 + */ +gchar * +gst_uri_to_string (const GstUri * uri) +{ + GString *uri_str; + gchar *escaped; + + g_return_val_if_fail (GST_IS_URI (uri), NULL); + + uri_str = g_string_new (NULL); + + if (uri->scheme != NULL) + g_string_append_printf (uri_str, "%s:", uri->scheme); + + if (uri->userinfo != NULL || uri->host != NULL || + uri->port != GST_URI_NO_PORT) + g_string_append (uri_str, "//"); + + if (uri->userinfo != NULL) { + escaped = _gst_uri_escape_userinfo (uri->userinfo); + g_string_append_printf (uri_str, "%s@", escaped); + g_free (escaped); + } + + if (uri->host != NULL) { + escaped = _gst_uri_escape_host (uri->host); + g_string_append (uri_str, escaped); + g_free (escaped); + } + + if (uri->port != GST_URI_NO_PORT) + g_string_append_printf (uri_str, ":%u", uri->port); + + if (uri->path != NULL) { + escaped = gst_uri_get_path_string (uri); + g_string_append (uri_str, escaped); + g_free (escaped); + } + + if (uri->query) { + g_string_append (uri_str, "?"); + escaped = gst_uri_get_query_string (uri); + g_string_append (uri_str, escaped); + g_free (escaped); + } + + if (uri->fragment != NULL) { + escaped = _gst_uri_escape_fragment (uri->fragment); + g_string_append_printf (uri_str, "#%s", escaped); + g_free (escaped); + } + + return g_string_free (uri_str, FALSE); +} + +/** + * gst_uri_is_normalized: + * @uri: The #GstUri to test to see if it is normalized. + * + * Tests the @uri to see if it is normalized. A %NULL @uri is considered to be + * normalized. + * + * Returns: TRUE if the URI is normalized or is %NULL. + * + * Since: 1.6 + */ +gboolean +gst_uri_is_normalized (const GstUri * uri) +{ + GList *new_path; + gboolean ret; + + if (uri == NULL) + return TRUE; + + g_return_val_if_fail (GST_IS_URI (uri), FALSE); + + /* check for non-normalized characters in uri parts */ + if (_gst_uri_first_non_normalized_char (uri->scheme, + _GST_URI_NORMALIZE_LOWERCASE) != NULL || + /*_gst_uri_first_non_normalized_char (uri->userinfo, + _GST_URI_NORMALIZE_PERCENTAGES) != NULL || */ + _gst_uri_first_non_normalized_char (uri->host, + _GST_URI_NORMALIZE_LOWERCASE /*| _GST_URI_NORMALIZE_PERCENTAGES */ ) + != NULL + /*|| _gst_uri_first_non_normalized_char (uri->path, + _GST_URI_NORMALIZE_PERCENTAGES) != NULL + || _gst_uri_first_non_normalized_char (uri->query, + _GST_URI_NORMALIZE_PERCENTAGES) != NULL + || _gst_uri_first_non_normalized_char (uri->fragment, + _GST_URI_NORMALIZE_PERCENTAGES) != NULL */ ) + return FALSE; + + /* also check path has had dot segments removed */ + new_path = _remove_dot_segments (uri->path); + ret = + (_gst_uri_compare_lists (new_path, uri->path, + (GCompareFunc) g_strcmp0) == 0); + g_list_free_full (new_path, g_free); + return ret; +} + +/** + * gst_uri_normalize: + * @uri: (transfer none): The #GstUri to normalize. + * + * Normalization will remove extra path segments ("." and "..") from the URI. It + * will also convert the scheme and host name to lower case and any + * percent-encoded values to uppercase. + * + * The #GstUri object must be writable. Check with gst_uri_is_writable() or use + * gst_uri_make_writable() first. + * + * Returns: TRUE if the URI was modified. + * + * Since: 1.6 + */ +gboolean +gst_uri_normalize (GstUri * uri) +{ + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + return _gst_uri_normalize_scheme (uri->scheme) | + _gst_uri_normalize_userinfo (uri->userinfo) | + _gst_uri_normalize_hostname (uri->host) | + _gst_uri_normalize_path (&uri->path) | + _gst_uri_normalize_query (uri->query) | + _gst_uri_normalize_fragment (uri->fragment); +} + +/** + * gst_uri_get_scheme: + * @uri: (nullable): This #GstUri object. + * + * Get the scheme name from the URI or %NULL if it doesn't exist. + * If @uri is %NULL then returns %NULL. + * + * Returns: The scheme from the #GstUri object or %NULL. + */ +const gchar * +gst_uri_get_scheme (const GstUri * uri) +{ + g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); + return (uri ? uri->scheme : NULL); +} + +/** + * gst_uri_set_scheme: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @scheme: The new scheme to set or %NULL to unset the scheme. + * + * Set or unset the scheme for the URI. + * + * Returns: %TRUE if the scheme was set/unset successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_scheme (GstUri * uri, const gchar * scheme) +{ + if (!uri) + return scheme == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + g_free (uri->scheme); + uri->scheme = g_strdup (scheme); + + return TRUE; +} + +/** + * gst_uri_get_userinfo: + * @uri: (nullable): This #GstUri object. + * + * Get the userinfo (usually in the form "username:password") from the URI + * or %NULL if it doesn't exist. If @uri is %NULL then returns %NULL. + * + * Returns: The userinfo from the #GstUri object or %NULL. + * + * Since: 1.6 + */ +const gchar * +gst_uri_get_userinfo (const GstUri * uri) +{ + g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); + return (uri ? uri->userinfo : NULL); +} + +/** + * gst_uri_set_userinfo: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @userinfo: The new user-information string to set or %NULL to unset. + * + * Set or unset the user information for the URI. + * + * Returns: %TRUE if the user information was set/unset successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_userinfo (GstUri * uri, const gchar * userinfo) +{ + if (!uri) + return userinfo == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + g_free (uri->userinfo); + uri->userinfo = g_strdup (userinfo); + + return TRUE; +} + +/** + * gst_uri_get_host: + * @uri: (nullable): This #GstUri object. + * + * Get the host name from the URI or %NULL if it doesn't exist. + * If @uri is %NULL then returns %NULL. + * + * Returns: The host name from the #GstUri object or %NULL. + * + * Since: 1.6 + */ +const gchar * +gst_uri_get_host (const GstUri * uri) +{ + g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); + return (uri ? uri->host : NULL); +} + +/** + * gst_uri_set_host: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @host: The new host string to set or %NULL to unset. + * + * Set or unset the host for the URI. + * + * Returns: %TRUE if the host was set/unset successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_host (GstUri * uri, const gchar * host) +{ + if (!uri) + return host == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + g_free (uri->host); + uri->host = g_strdup (host); + + return TRUE; +} + +/** + * gst_uri_get_port: + * @uri: (nullable): This #GstUri object. + * + * Get the port number from the URI or %GST_URI_NO_PORT if it doesn't exist. + * If @uri is %NULL then returns %GST_URI_NO_PORT. + * + * Returns: The port number from the #GstUri object or %GST_URI_NO_PORT. + * + * Since: 1.6 + */ +guint +gst_uri_get_port (const GstUri * uri) +{ + g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), GST_URI_NO_PORT); + return (uri ? uri->port : GST_URI_NO_PORT); +} + +/** + * gst_uri_set_port: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @port: The new port number to set or %GST_URI_NO_PORT to unset. + * + * Set or unset the port number for the URI. + * + * Returns: %TRUE if the port number was set/unset successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_port (GstUri * uri, guint port) +{ + if (!uri) + return port == GST_URI_NO_PORT; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + uri->port = port; + + return TRUE; +} + +/** + * gst_uri_get_path: + * @uri: The #GstUri to get the path from. + * + * Extract the path string from the URI object. + * + * Returns: (transfer full): The path from the URI. Once finished with the + * string should be g_free()'d. + * + * Since: 1.6 + */ +gchar * +gst_uri_get_path (const GstUri * uri) +{ + GList *path_segment; + const gchar *sep = ""; + GString *ret; + + if (!uri) + return NULL; + g_return_val_if_fail (GST_IS_URI (uri), NULL); + if (!uri->path) + return NULL; + + ret = g_string_new (NULL); + + for (path_segment = uri->path; path_segment; + path_segment = path_segment->next) { + g_string_append (ret, sep); + if (path_segment->data) { + g_string_append (ret, path_segment->data); + } + sep = "/"; + } + + return g_string_free (ret, FALSE); +} + +/** + * gst_uri_set_path: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @path: The new path to set with path segments separated by '/', or use %NULL + * to unset the path. + * + * Sets or unsets the path in the URI. + * + * Returns: %TRUE if the path was set successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_path (GstUri * uri, const gchar * path) +{ + if (!uri) + return path == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + g_list_free_full (uri->path, g_free); + uri->path = _gst_uri_string_to_list (path, "/", FALSE, FALSE); + + return TRUE; +} + +/** + * gst_uri_get_path_string: + * @uri: The #GstUri to get the path from. + * + * Extract the path string from the URI object as a percent encoded URI path. + * + * Returns: (transfer full): The path from the URI. Once finished with the + * string should be g_free()'d. + * + * Since: 1.6 + */ +gchar * +gst_uri_get_path_string (const GstUri * uri) +{ + GList *path_segment; + const gchar *sep = ""; + GString *ret; + gchar *escaped; + + if (!uri) + return NULL; + g_return_val_if_fail (GST_IS_URI (uri), NULL); + if (!uri->path) + return NULL; + + ret = g_string_new (NULL); + + for (path_segment = uri->path; path_segment; + path_segment = path_segment->next) { + g_string_append (ret, sep); + if (path_segment->data) { + escaped = _gst_uri_escape_path_segment (path_segment->data); + g_string_append (ret, escaped); + g_free (escaped); + } + sep = "/"; + } + + return g_string_free (ret, FALSE); +} + +/** + * gst_uri_set_path_string: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @path: The new percent encoded path to set with path segments separated by + * '/', or use %NULL to unset the path. + * + * Sets or unsets the path in the URI. + * + * Returns: %TRUE if the path was set successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_path_string (GstUri * uri, const gchar * path) +{ + if (!uri) + return path == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + g_list_free_full (uri->path, g_free); + uri->path = _gst_uri_string_to_list (path, "/", FALSE, TRUE); + return TRUE; +} + +/** + * gst_uri_get_path_segments: + * @uri: (nullable): The #GstUri to get the path from. + * + * Get a list of path segments from the URI. + * + * Returns: (transfer full)(element-type gchar*): A #GList of path segment + * strings or %NULL if no path segments are available. Free the list + * when no longer needed with g_list_free_full(list, g_free). + * + * Since: 1.6 + */ +GList * +gst_uri_get_path_segments (const GstUri * uri) +{ + GList *ret = NULL; + + g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); + + if (uri) { + ret = g_list_copy_deep (uri->path, (GCopyFunc) g_strdup, NULL); + } + + return ret; +} + +/** + * gst_uri_set_path_segments: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @path_segments: (transfer full)(nullable)(element-type gchar*): The new + * path list to set. + * + * Replace the path segments list in the URI. + * + * Returns: %TRUE if the path segments were set successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_path_segments (GstUri * uri, GList * path_segments) +{ + g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), FALSE); + + if (!uri) { + if (path_segments) + g_list_free_full (path_segments, g_free); + return path_segments == NULL; + } + + g_return_val_if_fail (gst_uri_is_writable (uri), FALSE); + + g_list_free_full (uri->path, g_free); + uri->path = path_segments; + return TRUE; +} + +/** + * gst_uri_append_path: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @relative_path: Relative path to append to the end of the current path. + * + * Append a path onto the end of the path in the URI. The path is not + * normalized, call #gst_uri_normalize() to normalize the path. + * + * Returns: %TRUE if the path was appended successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_append_path (GstUri * uri, const gchar * relative_path) +{ + GList *rel_path_list; + + if (!uri) + return relative_path == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + if (!relative_path) + return TRUE; + + if (uri->path) { + GList *last_elem = g_list_last (uri->path); + if (last_elem->data == NULL) { + uri->path = g_list_delete_link (uri->path, last_elem); + } + } + rel_path_list = _gst_uri_string_to_list (relative_path, "/", FALSE, FALSE); + /* if path was absolute, make it relative by removing initial NULL element */ + if (rel_path_list && rel_path_list->data == NULL) { + rel_path_list = g_list_delete_link (rel_path_list, rel_path_list); + } + uri->path = g_list_concat (uri->path, rel_path_list); + return TRUE; +} + +/** + * gst_uri_append_path_segment: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @path_segment: The path segment string to append to the URI path. + * + * Append a single path segment onto the end of the URI path. + * + * Returns: %TRUE if the path was appended successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_append_path_segment (GstUri * uri, const gchar * path_segment) +{ + if (!uri) + return path_segment == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + if (!path_segment) + return TRUE; + + /* if base path ends in a directory (i.e. last element is NULL), remove it */ + if (uri->path && g_list_last (uri->path)->data == NULL) { + uri->path = g_list_delete_link (uri->path, g_list_last (uri->path)); + } + uri->path = g_list_append (uri->path, g_strdup (path_segment)); + return TRUE; +} + +/** + * gst_uri_get_query_string: + * @uri: (nullable): The #GstUri to get the query string from. + * + * Get a percent encoded URI query string from the @uri. + * + * Returns: (transfer full): A percent encoded query string. Use g_free() when + * no longer needed. + * + * Since: 1.6 + */ +gchar * +gst_uri_get_query_string (const GstUri * uri) +{ + GHashTableIter iter; + gpointer key, value; + const gchar *sep = ""; + gchar *escaped; + GString *ret; + + if (!uri) + return NULL; + g_return_val_if_fail (GST_IS_URI (uri), NULL); + if (!uri->query) + return NULL; + + ret = g_string_new (NULL); + g_hash_table_iter_init (&iter, uri->query); + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_string_append (ret, sep); + escaped = _gst_uri_escape_http_query_element (key); + g_string_append (ret, escaped); + g_free (escaped); + if (value) { + escaped = _gst_uri_escape_http_query_element (value); + g_string_append_printf (ret, "=%s", escaped); + g_free (escaped); + } + sep = "&"; + } + + return g_string_free (ret, FALSE); +} + +/** + * gst_uri_set_query_string: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @query: The new percent encoded query string to use to populate the query + * table, or use %NULL to unset the query table. + * + * Sets or unsets the query table in the URI. + * + * Returns: %TRUE if the query table was set successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_query_string (GstUri * uri, const gchar * query) +{ + if (!uri) + return query == NULL; + + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + if (uri->query) + g_hash_table_unref (uri->query); + uri->query = _gst_uri_string_to_table (query, "&", "=", TRUE, TRUE); + + return TRUE; +} + +/** + * gst_uri_get_query_table: + * @uri: (nullable): The #GstUri to get the query table from. + * + * Get the query table from the URI. Keys and values in the table are freed + * with g_free when they are deleted. A value may be %NULL to indicate that + * the key should appear in the query string in the URI, but does not have a + * value. Free the returned #GHashTable with #g_hash_table_unref() when it is + * no longer required. Modifying this hash table will modify the query in the + * URI. + * + * Returns: (transfer full)(element-type gchar* gchar*): The query hash table + * from the URI. + * + * Since: 1.6 + */ +GHashTable * +gst_uri_get_query_table (const GstUri * uri) +{ + if (!uri) + return NULL; + g_return_val_if_fail (GST_IS_URI (uri), NULL); + if (!uri->query) + return NULL; + + return g_hash_table_ref (uri->query); +} + +/** + * gst_uri_set_query_table: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @query_table: (transfer none)(nullable)(element-type gchar* gchar*): The new + * query table to use. + * + * Set the query table to use in the URI. The old table is unreferenced and a + * reference to the new one is used instead. A value if %NULL for @query_table + * will remove the query string from the URI. + * + * Returns: %TRUE if the new table was sucessfully used for the query table. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_query_table (GstUri * uri, GHashTable * query_table) +{ + GHashTable *old_table = NULL; + + if (!uri) + return query_table == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + old_table = uri->query; + if (query_table) + uri->query = g_hash_table_ref (query_table); + else + uri->query = NULL; + if (old_table) + g_hash_table_unref (old_table); + + return TRUE; +} + +/** + * gst_uri_set_query_value: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @query_key: (transfer none): The key for the query entry. + * @query_value: (transfer none)(nullable): The value for the key. + * + * This inserts or replaces a key in the query table. A @query_value of %NULL + * indicates that the key has no associated value, but will still be present in + * the query string. + * + * Returns: %TRUE if the query table was sucessfully updated. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_query_value (GstUri * uri, const gchar * query_key, + const gchar * query_value) +{ + if (!uri) + return FALSE; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + if (!uri->query) { + uri->query = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + g_free); + } + g_hash_table_insert (uri->query, g_strdup (query_key), + g_strdup (query_value)); + + return TRUE; +} + +/** + * gst_uri_remove_query_key: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @query_key: The key to remove. + * + * Remove an entry from the query table by key. + * + * Returns: %TRUE if the key existed in the table and was removed. + * + * Since: 1.6 + */ +gboolean +gst_uri_remove_query_key (GstUri * uri, const gchar * query_key) +{ + gboolean result; + + if (!uri) + return FALSE; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + if (!uri->query) + return FALSE; + + result = g_hash_table_remove (uri->query, query_key); + /* if this was the last query entry, remove the query string completely */ + if (result && g_hash_table_size (uri->query) == 0) { + g_hash_table_unref (uri->query); + uri->query = NULL; + } + return result; +} + +/** + * gst_uri_query_has_key: + * @uri: (nullable): The #GstUri to examine. + * @query_key: The key to lookup. + * + * Check if there is a query table entry for the @query_key key. + * + * Returns: %TRUE if @query_key exists in the URI query table. + * + * Since: 1.6 + */ +gboolean +gst_uri_query_has_key (const GstUri * uri, const gchar * query_key) +{ + if (!uri) + return FALSE; + g_return_val_if_fail (GST_IS_URI (uri), FALSE); + if (!uri->query) + return FALSE; + + return g_hash_table_contains (uri->query, query_key); +} + +/** + * gst_uri_get_query_value: + * @uri: (nullable): The #GstUri to examine. + * @query_key: The key to lookup. + * + * Get the value associated with the @query_key key. Will return %NULL if the + * key has no value or if the key does not exist in the URI query table. Because + * %NULL is returned for both missing keys and keys with no value, you should + * use gst_uri_query_has_key() to determine if a key is present in the URI + * query. + * + * Returns: The value for the given key, or %NULL if not found. + * + * Since: 1.6 + */ +const gchar * +gst_uri_get_query_value (const GstUri * uri, const gchar * query_key) +{ + if (!uri) + return NULL; + g_return_val_if_fail (GST_IS_URI (uri), NULL); + if (!uri->query) + return NULL; + + return g_hash_table_lookup (uri->query, query_key); +} + +/** + * gst_uri_get_query_keys: + * @uri: (nullable): The #GstUri to examine. + * + * Get a list of the query keys from the URI. + * + * Returns: (transfer container)(element-type gchar*): A list of keys from + * the URI query. Free the list with g_list_free(). + * + * Since: 1.6 + */ +GList * +gst_uri_get_query_keys (const GstUri * uri) +{ + if (!uri) + return NULL; + g_return_val_if_fail (GST_IS_URI (uri), NULL); + if (!uri->query) + return NULL; + + return g_hash_table_get_keys (uri->query); +} + +/** + * gst_uri_get_fragment: + * @uri: (nullable): This #GstUri object. + * + * Get the fragment name from the URI or %NULL if it doesn't exist. + * If @uri is %NULL then returns %NULL. + * + * Returns: The host name from the #GstUri object or %NULL. + * + * Since: 1.6 + */ +const gchar * +gst_uri_get_fragment (const GstUri * uri) +{ + g_return_val_if_fail (uri == NULL || GST_IS_URI (uri), NULL); + return (uri ? uri->fragment : NULL); +} + +/** + * gst_uri_set_fragment: + * @uri: (transfer none)(nullable): The #GstUri to modify. + * @fragment: (nullable): The fragment string to set. + * + * Sets the fragment string in the URI. Use a value of %NULL in @fragment to + * unset the fragment string. + * + * Returns: %TRUE if the fragment was set/unset successfully. + * + * Since: 1.6 + */ +gboolean +gst_uri_set_fragment (GstUri * uri, const gchar * fragment) +{ + if (!uri) + return fragment == NULL; + g_return_val_if_fail (GST_IS_URI (uri) && gst_uri_is_writable (uri), FALSE); + + g_free (uri->fragment); + uri->fragment = g_strdup (fragment); + return TRUE; +} diff --git a/gst/gsturi.h b/gst/gsturi.h index 8a4a9cc..2542c15 100644 --- a/gst/gsturi.h +++ b/gst/gsturi.h @@ -1,8 +1,10 @@ /* GStreamer * Copyright (C) 1999,2000 Erik Walthinsen * 2000 Wim Taymans + * 2014 David Waring, British Broadcasting Corporation + * * - * gsturi.h: Header for uri to element mappings + * gsturi.h: Header for uri to element mappings and URI manipulation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -25,7 +27,11 @@ #define __GST_URI_H__ #include +#include #include +#include + +#include "gstminiobject.h" G_BEGIN_DECLS @@ -151,6 +157,159 @@ gboolean gst_uri_handler_set_uri (GstURIHandler * handler, const gchar * uri, GError ** error); +/* + * GstUri Type macros. + */ +#define GST_TYPE_URI (gst_uri_get_type ()) +#define GST_IS_URI(obj) (GST_IS_MINI_OBJECT_TYPE (obj, GST_TYPE_URI)) +#define GST_URI_CAST(obj) ((GstUri *)(obj)) +#define GST_URI_CONST_CAST(obj) ((const GstUri *)(obj)) +#define GST_URI(obj) (GST_URI_CAST(obj)) + +/** + * GstUri: + * + * This is a private structure that holds the various parts of a parsed URI. + */ +struct _GstUri; +typedef struct _GstUri GstUri; + +/** + * GST_URI_NO_PORT: + * + * Value for #GstUri.port to indicate no port number. + */ +#define GST_URI_NO_PORT 0 + +/* used by GST_TYPE_URI */ +GType gst_uri_get_type (void); + +/* + * Method definitions. + */ + +GstUri * gst_uri_new (const gchar * scheme, + const gchar * userinfo, + const gchar * host, + guint port, + const gchar * path, + const gchar * query, + const gchar * fragment) G_GNUC_MALLOC; +GstUri * gst_uri_new_with_base (GstUri * base, + const gchar * scheme, + const gchar * userinfo, + const gchar * host, + guint port, + const gchar * path, + const gchar * query, + const gchar * fragment) G_GNUC_MALLOC; +GstUri * gst_uri_from_string (const gchar * uri) G_GNUC_MALLOC; +GstUri * gst_uri_from_string_with_base (GstUri * base, + const gchar * uri) G_GNUC_MALLOC; +gboolean gst_uri_equal (const GstUri * first, + const GstUri * second); +GstUri * gst_uri_join (GstUri * base_uri, + GstUri * ref_uri); +gchar * gst_uri_join_strings (const gchar * base_uri, + const gchar * ref_uri) G_GNUC_MALLOC; +gboolean gst_uri_is_writable (const GstUri * uri); +GstUri * gst_uri_make_writable (GstUri * uri); +gchar * gst_uri_to_string (const GstUri * uri) G_GNUC_MALLOC; +gboolean gst_uri_is_normalized (const GstUri * uri); +gboolean gst_uri_normalize (GstUri * uri); +const gchar * gst_uri_get_scheme (const GstUri * uri); +gboolean gst_uri_set_scheme (GstUri * uri, const gchar * scheme); +const gchar * gst_uri_get_userinfo (const GstUri * uri); +gboolean gst_uri_set_userinfo (GstUri * uri, const gchar * userinfo); +const gchar * gst_uri_get_host (const GstUri * uri); +gboolean gst_uri_set_host (GstUri * uri, const gchar * host); +guint gst_uri_get_port (const GstUri * uri); +gboolean gst_uri_set_port (GstUri * uri, guint port); +gchar * gst_uri_get_path (const GstUri * uri); +gboolean gst_uri_set_path (GstUri * uri, const gchar * path); +gchar * gst_uri_get_path_string (const GstUri * uri); +gboolean gst_uri_set_path_string (GstUri * uri, const gchar * path); +GList * gst_uri_get_path_segments (const GstUri * uri); +gboolean gst_uri_set_path_segments (GstUri * uri, GList * path_segments); +gboolean gst_uri_append_path (GstUri * uri, + const gchar * relative_path); +gboolean gst_uri_append_path_segment (GstUri * uri, + const gchar * path_segment); +gchar * gst_uri_get_query_string (const GstUri * uri); +gboolean gst_uri_set_query_string (GstUri * uri, const gchar * query); +GHashTable * gst_uri_get_query_table (const GstUri * uri); +gboolean gst_uri_set_query_table (GstUri * uri, + GHashTable * query_table); +gboolean gst_uri_set_query_value (GstUri * uri, const gchar * query_key, + const gchar * query_value); +gboolean gst_uri_remove_query_key (GstUri * uri, const gchar * query_key); +gboolean gst_uri_query_has_key (const GstUri * uri, + const gchar * query_key); +const gchar * gst_uri_get_query_value (const GstUri * uri, + const gchar * query_key); +GList * gst_uri_get_query_keys (const GstUri * uri); +const gchar * gst_uri_get_fragment (const GstUri * uri); +gboolean gst_uri_set_fragment (GstUri * uri, const gchar * fragment); + +/** + * gst_uri_copy: + * @uri: This #GstUri object. + * + * Create a new #GstUri object with the same data as this #GstUri object. + * If @uri is %NULL then returns %NULL. + * + * Returns: (transfer full): A new #GstUri object which is a copy of this + * #GstUri or %NULL. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstUri * gst_uri_copy (const GstUri * uri); +#endif + +static inline GstUri * +gst_uri_copy (const GstUri * uri) +{ + return GST_URI_CAST (gst_mini_object_copy (GST_MINI_OBJECT_CONST_CAST (uri))); +} + +/** + * gst_uri_ref: + * @uri: (transfer none): This #GstUri object. + * + * Add a reference to this #GstUri object. See gst_mini_object_ref() for further + * info. + * + * Returns: This object with the reference count incremented. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC GstUri * gst_uri_ref (GstUri * uri); +#endif + +static inline GstUri * +gst_uri_ref (GstUri * uri) +{ + return GST_URI_CAST (gst_mini_object_ref (GST_MINI_OBJECT_CAST (uri))); +} + +/** + * gst_uri_unref: + * @uri: (transfer full): This #GstUri object. + * + * Decrement the reference count to this #GstUri object. + * + * If the reference count drops to 0 then finalize this object. + * + * See gst_mini_object_unref() for further info. + */ +#ifdef _FOOL_GTK_DOC_ +G_INLINE_FUNC void gst_uri_unref (GstUri * uri); +#endif + +static inline void +gst_uri_unref (GstUri * uri) +{ + gst_mini_object_unref (GST_MINI_OBJECT_CAST (uri)); +} + G_END_DECLS #endif /* __GST_URI_H__ */ diff --git a/tests/check/gst/gsturi.c b/tests/check/gst/gsturi.c index 1d53265..06b2661 100644 --- a/tests/check/gst/gsturi.c +++ b/tests/check/gst/gsturi.c @@ -164,6 +164,594 @@ GST_START_TEST (test_element_make_from_uri) GST_END_TEST; +GST_START_TEST (test_url_parsing) +{ + GstUri *url; + GList *list; + gchar *tmp_str; + + url = + gst_uri_from_string + ("scheme://user:pass@host.com:1234/path/to/item-obj?query=something#fragment"); + + fail_unless_equals_string (gst_uri_get_scheme (url), "scheme"); + fail_unless_equals_string (gst_uri_get_userinfo (url), "user:pass"); + fail_unless_equals_string (gst_uri_get_host (url), "host.com"); + fail_unless (gst_uri_get_port (url) == 1234); + tmp_str = gst_uri_get_path (url); + fail_unless_equals_string (tmp_str, "/path/to/item-obj"); + g_free (tmp_str); + list = gst_uri_get_query_keys (url); + fail_unless (g_list_length (list) == 1); + g_list_free (list); + fail_unless (gst_uri_query_has_key (url, "query")); + fail_unless_equals_string (gst_uri_get_query_value (url, "query"), + "something"); + fail_unless_equals_string (gst_uri_get_fragment (url), "fragment"); + gst_uri_unref (url); + + url = gst_uri_from_string ("scheme://host/path/to/dir/"); + fail_unless_equals_string (gst_uri_get_scheme (url), "scheme"); + fail_unless (gst_uri_get_userinfo (url) == NULL); + fail_unless_equals_string (gst_uri_get_host (url), "host"); + fail_unless (gst_uri_get_port (url) == GST_URI_NO_PORT); + tmp_str = gst_uri_get_path (url); + fail_unless_equals_string (tmp_str, "/path/to/dir/"); + g_free (tmp_str); + fail_unless (gst_uri_get_query_table (url) == NULL); + fail_unless (gst_uri_get_fragment (url) == NULL); + gst_uri_unref (url); + + url = gst_uri_from_string ("urn:name:path"); + fail_unless_equals_string (gst_uri_get_scheme (url), "urn"); + fail_unless (gst_uri_get_userinfo (url) == NULL); + fail_unless (gst_uri_get_host (url) == NULL); + fail_unless (gst_uri_get_port (url) == GST_URI_NO_PORT); + tmp_str = gst_uri_get_path (url); + fail_unless_equals_string (tmp_str, "name:path"); + g_free (tmp_str); + list = gst_uri_get_query_keys (url); + fail_unless (g_list_length (list) == 0); + g_list_free (list); + fail_unless (gst_uri_get_fragment (url) == NULL); + gst_uri_unref (url); +} + +GST_END_TEST; + +GST_START_TEST (test_url_normalization) +{ + GstUri *url; + gchar *tmp_str; + + url = + gst_uri_from_string + ("ScHeMe://User:P%61ss@HOST.%63om:1234/path/./from/../to%7d/item%2dobj?qu%65ry=something#fr%61gment"); + fail_unless (gst_uri_normalize (url)); + fail_unless_equals_string (gst_uri_get_scheme (url), "scheme"); + fail_unless_equals_string (gst_uri_get_userinfo (url), "User:Pass"); + fail_unless_equals_string (gst_uri_get_host (url), "host.com"); + tmp_str = gst_uri_get_path (url); + fail_unless_equals_string (tmp_str, "/path/to}/item-obj"); + g_free (tmp_str); + fail_unless (gst_uri_query_has_key (url, "query")); + fail_unless_equals_string (gst_uri_get_query_value (url, "query"), + "something"); + fail_unless_equals_string (gst_uri_get_fragment (url), "fragment"); + gst_uri_unref (url); +} + +GST_END_TEST; + +GST_START_TEST (test_url_joining) +{ + GstUri *base, *rel, *joined; + gchar *l; + + base = + gst_uri_from_string + ("http://example.com/path/to/dir/filename.html#fragment"); + + /* test change of fragment only */ + rel = gst_uri_from_string ("#new_frag"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/dir/filename.html#new_frag"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test addition of new query string */ + rel = gst_uri_from_string ("?key=val"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/dir/filename.html?key=val"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test new base filename */ + rel = gst_uri_from_string ("new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/dir/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test relative file same directory */ + rel = gst_uri_from_string ("./new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/dir/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test relative file parent directory */ + rel = gst_uri_from_string ("../new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, "http://example.com/path/to/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test relative file grandparent directory */ + rel = gst_uri_from_string ("../../new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, "http://example.com/path/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test relative file root directory */ + rel = gst_uri_from_string ("../../../new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, "http://example.com/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test relative file beyond root directory */ + rel = gst_uri_from_string ("../../../../new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, "http://example.com/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test add subdirectory */ + rel = gst_uri_from_string ("subdir/new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/dir/subdir/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test change directory */ + rel = gst_uri_from_string ("../subdir/new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/subdir/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + gst_uri_unref (base); + + /* change base for path ending in directory */ + base = gst_uri_from_string ("http://example.com/path/to/dir/"); + + /* test adding file to directory */ + rel = gst_uri_from_string ("new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/dir/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test adding file to directory using relative path */ + rel = gst_uri_from_string ("./new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, + "http://example.com/path/to/dir/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test filename in parent directory */ + rel = gst_uri_from_string ("../new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, "http://example.com/path/to/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + /* test replace with absolute */ + rel = gst_uri_from_string ("https://ssl.example.com/new_filename.xml"); + joined = gst_uri_join (base, rel); + l = gst_uri_to_string (joined); + fail_unless_equals_string (l, "https://ssl.example.com/new_filename.xml"); + g_free (l); + gst_uri_unref (joined); + gst_uri_unref (rel); + + gst_uri_unref (base); +} + +GST_END_TEST; + +GST_START_TEST (test_url_equality) +{ + GstUri *url1, *url2; + + url1 = + gst_uri_from_string + ("ScHeMe://User:Pass@HOST.com:1234/path/./from/../to%7d/item%2dobj?query=something#fragment"); + + /* equal */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment"); + fail_unless (gst_uri_equal (url1, url2)); + fail_unless (gst_uri_equal (url2, url1)); + gst_uri_unref (url2); + + /* different fragment */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something#different-fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different query */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=different-something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different path */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:1234/path/to%7D/different-item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different port */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:4321/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different host */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@different-host.com:1234/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different userinfo */ + url2 = + gst_uri_from_string + ("scheme://Different-User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different scheme */ + url2 = + gst_uri_from_string + ("different+scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different (no scheme) */ + url2 = + gst_uri_from_string + ("//User:Pass@host.com:1234/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different (no userinfo) */ + url2 = + gst_uri_from_string + ("scheme://host.com:1234/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different (no host) */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@:1234/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different (no port) */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com/path/to%7D/item-obj?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different (no path) */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:1234?query=something#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different (no query) */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj#fragment"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* different (no fragment) */ + url2 = + gst_uri_from_string + ("scheme://User:Pass@host.com:1234/path/to%7D/item-obj?query=something"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + /* compare two NULL uris */ + fail_unless (gst_uri_equal (NULL, NULL)); + + /* compare same object */ + fail_unless (gst_uri_equal (url1, url1)); + + /* compare one NULL and one non-NULL uri */ + fail_unless (!gst_uri_equal (url1, NULL)); + fail_unless (!gst_uri_equal (NULL, url1)); + + gst_uri_unref (url1); +} + +GST_END_TEST; + +GST_START_TEST (test_url_constructors) +{ + GstUri *url1, *url2; + gchar *tmp_str; + GHashTable *tmp_table; + + url1 = + gst_uri_new ("scheme", "userinfo", "hostname", 1234, "/path/to/file", + "query", "fragment"); + fail_unless_equals_string (gst_uri_get_scheme (url1), "scheme"); + fail_unless_equals_string (gst_uri_get_userinfo (url1), "userinfo"); + fail_unless_equals_string (gst_uri_get_host (url1), "hostname"); + fail_unless (gst_uri_get_port (url1) == 1234); + tmp_str = gst_uri_get_path (url1); + fail_unless_equals_string (tmp_str, "/path/to/file"); + g_free (tmp_str); + tmp_table = gst_uri_get_query_table (url1); + fail_unless (g_hash_table_size (tmp_table) == 1); + fail_unless (g_hash_table_contains (tmp_table, "query")); + fail_unless (g_hash_table_lookup (tmp_table, "query") == NULL); + g_hash_table_unref (tmp_table); + fail_unless_equals_string (gst_uri_get_fragment (url1), "fragment"); + tmp_str = gst_uri_to_string (url1); + fail_unless_equals_string (tmp_str, + "scheme://userinfo@hostname:1234/path/to/file?query#fragment"); + g_free (tmp_str); + + url2 = + gst_uri_new_with_base (url1, NULL, NULL, NULL, GST_URI_NO_PORT, + "new_file", NULL, NULL); + fail_unless_equals_string (gst_uri_get_scheme (url2), "scheme"); + fail_unless_equals_string (gst_uri_get_userinfo (url2), "userinfo"); + fail_unless_equals_string (gst_uri_get_host (url2), "hostname"); + fail_unless (gst_uri_get_port (url2) == 1234); + tmp_str = gst_uri_get_path (url2); + fail_unless_equals_string (tmp_str, "/path/to/new_file"); + g_free (tmp_str); + fail_unless (gst_uri_get_query_table (url2) == NULL); + fail_unless (gst_uri_get_fragment (url2) == NULL); + tmp_str = gst_uri_to_string (url2); + fail_unless_equals_string (tmp_str, + "scheme://userinfo@hostname:1234/path/to/new_file"); + g_free (tmp_str); + gst_uri_unref (url2); + + url2 = gst_uri_from_string_with_base (url1, "/a/new/path/to/file"); + fail_unless_equals_string (gst_uri_get_scheme (url2), "scheme"); + fail_unless_equals_string (gst_uri_get_userinfo (url2), "userinfo"); + fail_unless_equals_string (gst_uri_get_host (url2), "hostname"); + fail_unless (gst_uri_get_port (url2) == 1234); + tmp_str = gst_uri_get_path (url2); + fail_unless_equals_string (tmp_str, "/a/new/path/to/file"); + g_free (tmp_str); + fail_unless (gst_uri_get_query_table (url2) == NULL); + fail_unless (gst_uri_get_fragment (url2) == NULL); + tmp_str = gst_uri_to_string (url2); + fail_unless_equals_string (tmp_str, + "scheme://userinfo@hostname:1234/a/new/path/to/file"); + g_free (tmp_str); + gst_uri_unref (url2); + + url2 = gst_uri_copy (url1); + fail_unless (gst_uri_equal (url1, url2)); + gst_uri_set_query_value (url2, "key", "value"); + fail_unless (!gst_uri_equal (url1, url2)); + gst_uri_unref (url2); + + gst_uri_unref (url1); +} + +GST_END_TEST; + +GST_START_TEST (test_url_get_set) +{ + GstUri *url; + gchar *tmp_str; + GList *tmp_list; + + url = gst_uri_from_string ("scheme://hostname/path/to/file?query#fragment"); + + fail_unless (gst_uri_set_scheme (url, "new+scheme")); + fail_unless_equals_string (gst_uri_get_scheme (url), "new+scheme"); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "new+scheme://hostname/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (gst_uri_set_scheme (url, NULL)); + fail_unless (gst_uri_get_scheme (url) == NULL); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, "//hostname/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_set_scheme (NULL, "fail")); + fail_unless (gst_uri_set_scheme (NULL, NULL)); + + fail_unless (gst_uri_set_userinfo (url, "username:password")); + fail_unless_equals_string (gst_uri_get_userinfo (url), "username:password"); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//username:password@hostname/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (gst_uri_set_userinfo (url, NULL)); + fail_unless (gst_uri_get_userinfo (url) == NULL); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, "//hostname/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_set_userinfo (NULL, "fail")); + fail_unless (gst_uri_set_userinfo (NULL, NULL)); + + fail_unless (gst_uri_set_host (url, NULL)); + fail_unless (gst_uri_get_host (url) == NULL); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, "/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (gst_uri_set_host (url, "example.com")); + fail_unless_equals_string (gst_uri_get_host (url), "example.com"); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_set_host (NULL, "fail")); + fail_unless (gst_uri_set_host (NULL, NULL)); + + fail_unless (gst_uri_set_port (url, 12345)); + fail_unless (gst_uri_get_port (url) == 12345); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com:12345/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (gst_uri_set_port (url, GST_URI_NO_PORT)); + fail_unless (gst_uri_get_port (url) == GST_URI_NO_PORT); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file?query#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_set_port (NULL, 1234)); + fail_unless (gst_uri_set_port (NULL, GST_URI_NO_PORT)); + + fail_unless (gst_uri_append_path_segment (url, "here")); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/here?query#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_append_path_segment (NULL, "fail")); + fail_unless (gst_uri_append_path_segment (NULL, NULL)); + + fail_unless (gst_uri_append_path (url, "../there")); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/here/../there?query#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_append_path (NULL, "fail")); + fail_unless (gst_uri_append_path (NULL, NULL)); + + gst_uri_normalize (url); + + tmp_list = gst_uri_get_path_segments (url); + fail_unless (tmp_list != NULL); + tmp_list = g_list_append (tmp_list, g_strdup ("segment")); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/there?query#fragment"); + g_free (tmp_str); + fail_unless (gst_uri_set_path_segments (url, tmp_list)); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/there/segment?query#fragment"); + g_free (tmp_str); + + tmp_list = g_list_append (NULL, g_strdup ("test")); + fail_unless (!gst_uri_set_path_segments (NULL, tmp_list)); + fail_unless (gst_uri_set_path_segments (NULL, NULL)); + + fail_unless (gst_uri_set_query_value (url, "key", "value")); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/there/segment?query&key=value#fragment"); + g_free (tmp_str); + + fail_unless (gst_uri_set_query_value (url, "key", NULL)); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/there/segment?query&key#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_set_query_value (NULL, "key", "value")); + + fail_unless (gst_uri_remove_query_key (url, "key")); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/there/segment?query#fragment"); + g_free (tmp_str); + + fail_unless (!gst_uri_remove_query_key (url, "key")); + fail_unless (!gst_uri_remove_query_key (NULL, "key")); + + fail_unless (gst_uri_set_fragment (url, NULL)); + fail_unless (gst_uri_get_fragment (url) == NULL); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/there/segment?query"); + g_free (tmp_str); + + fail_unless (gst_uri_set_fragment (url, "tag")); + fail_unless_equals_string (gst_uri_get_fragment (url), "tag"); + tmp_str = gst_uri_to_string (url); + fail_unless_equals_string (tmp_str, + "//example.com/path/to/file/there/segment?query#tag"); + g_free (tmp_str); + + fail_unless (!gst_uri_set_fragment (NULL, "can't set if no URI")); + fail_unless (gst_uri_set_fragment (NULL, NULL)); + + gst_uri_unref (url); +} + +GST_END_TEST; + static Suite * gst_uri_suite (void) { @@ -180,6 +768,12 @@ gst_uri_suite (void) #ifdef G_OS_WIN32 tcase_add_test (tc_chain, test_win32_uri); #endif + tcase_add_test (tc_chain, test_url_parsing); + tcase_add_test (tc_chain, test_url_normalization); + tcase_add_test (tc_chain, test_url_joining); + tcase_add_test (tc_chain, test_url_equality); + tcase_add_test (tc_chain, test_url_constructors); + tcase_add_test (tc_chain, test_url_get_set); return s; } diff --git a/win32/common/libgstreamer.def b/win32/common/libgstreamer.def index 2fda6b6..b83293b 100644 --- a/win32/common/libgstreamer.def +++ b/win32/common/libgstreamer.def @@ -1345,20 +1345,60 @@ EXPORTS gst_type_find_suggest gst_type_find_suggest_simple gst_update_registry + gst_uri_append_path + gst_uri_append_path_segment gst_uri_construct + gst_uri_equal gst_uri_error_get_type gst_uri_error_quark + gst_uri_from_string + gst_uri_from_string_with_base + gst_uri_get_fragment + gst_uri_get_host gst_uri_get_location + gst_uri_get_path + gst_uri_get_path_segments + gst_uri_get_path_string + gst_uri_get_port gst_uri_get_protocol + gst_uri_get_query_keys + gst_uri_get_query_string + gst_uri_get_query_table + gst_uri_get_query_value + gst_uri_get_scheme + gst_uri_get_type + gst_uri_get_userinfo gst_uri_handler_get_protocols gst_uri_handler_get_type gst_uri_handler_get_uri gst_uri_handler_get_uri_type gst_uri_handler_set_uri gst_uri_has_protocol + gst_uri_is_normalized gst_uri_is_valid + gst_uri_is_writable + gst_uri_join + gst_uri_join_strings + gst_uri_make_writable + gst_uri_new + gst_uri_new_with_base + gst_uri_normalize gst_uri_protocol_is_supported gst_uri_protocol_is_valid + gst_uri_query_has_key + gst_uri_remove_query_key + gst_uri_set_fragment + gst_uri_set_host + gst_uri_set_path + gst_uri_set_path_segments + gst_uri_set_path_string + gst_uri_set_port + gst_uri_set_query_string + gst_uri_set_query_table + gst_uri_set_query_value + gst_uri_set_scheme + gst_uri_set_userinfo + gst_uri_to_string gst_uri_type_get_type gst_util_array_binary_search gst_util_double_to_fraction -- 2.7.4