gsturi: Add new API for storing unmodified userinfo / fragment

New API: gst_uri_from_string_escaped()

Identical to gst_uri_from_string() except that the userinfo and
fragment components of the URI will not be unescaped while parsing.

This is needed for correctly parsing usernames or passwords with `:`
in them such as reported at:
https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/831

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/583>
This commit is contained in:
Nirbheek Chauhan 2020-07-30 19:31:55 +05:30 committed by GStreamer Merge Bot
parent 5f4723d842
commit 5195ad9126
3 changed files with 134 additions and 16 deletions

View file

@ -1497,19 +1497,8 @@ gst_uri_new_with_base (GstUri * base, const gchar * scheme,
return new_uri;
}
/**
* gst_uri_from_string:
* @uri: The URI string to parse.
*
* Parses a URI string into a new #GstUri object. Will return NULL if the URI
* cannot be parsed.
*
* Returns: (transfer full) (nullable): A new #GstUri object, or NULL.
*
* Since: 1.6
*/
GstUri *
gst_uri_from_string (const gchar * uri)
static GstUri *
_gst_uri_from_string_internal (const gchar * uri, gboolean unescape)
{
const gchar *orig_uri = uri;
GstUri *uri_obj;
@ -1545,7 +1534,10 @@ gst_uri_from_string (const gchar * uri)
/* find end of userinfo */
eoui = strchr (uri, '@');
if (eoui != NULL && eoui < eoa) {
uri_obj->userinfo = g_uri_unescape_segment (uri, eoui, NULL);
if (unescape)
uri_obj->userinfo = g_uri_unescape_segment (uri, eoui, NULL);
else
uri_obj->userinfo = g_strndup (uri, eoui - uri);
uri = eoui + 1;
}
/* find end of host */
@ -1565,8 +1557,10 @@ gst_uri_from_string (const gchar * uri)
reoh = eoh = eoa;
}
/* don't capture empty host strings */
if (eoh != uri)
if (eoh != uri) {
/* always unescape hostname */
uri_obj->host = g_uri_unescape_segment (uri, eoh, NULL);
}
uri = reoh;
if (uri < eoa) {
@ -1620,13 +1614,61 @@ gst_uri_from_string (const gchar * uri)
}
}
if (uri != NULL && uri[0] == '#') {
uri_obj->fragment = g_uri_unescape_string (uri + 1, NULL);
if (unescape)
uri_obj->fragment = g_uri_unescape_string (uri + 1, NULL);
else
uri_obj->fragment = g_strdup (uri + 1);
}
}
return uri_obj;
}
/**
* gst_uri_from_string:
* @uri: The URI string to parse.
*
* Parses a URI string into a new #GstUri object. Will return NULL if the URI
* cannot be parsed.
*
* Returns: (transfer full) (nullable): A new #GstUri object, or NULL.
*
* Since: 1.6
*/
GstUri *
gst_uri_from_string (const gchar * uri)
{
return _gst_uri_from_string_internal (uri, TRUE);
}
/**
* gst_uri_from_string_escaped:
* @uri: The URI string to parse.
*
* Parses a URI string into a new #GstUri object. Will return NULL if the URI
* cannot be parsed. This is identical to gst_uri_from_string() except that
* the userinfo and fragment components of the URI will not be unescaped while
* parsing.
*
* Use this when you need to extract a username and password from the userinfo
* such as https://user:password@example.com since either may contain
* a URI-escaped ':' character. gst_uri_from_string() will unescape the entire
* userinfo component, which will make it impossible to know which ':'
* delineates the username and password.
*
* The same applies to the fragment component of the URI, such as
* https://example.com/path#fragment which may contain a URI-escaped '#'.
*
* Returns: (transfer full) (nullable): A new #GstUri object, or NULL.
*
* Since: 1.18
*/
GstUri *
gst_uri_from_string_escaped (const gchar * uri)
{
return _gst_uri_from_string_internal (uri, FALSE);
}
/**
* gst_uri_from_string_with_base:
* @base: (transfer none)(nullable): The base URI to join the new URI with.

View file

@ -235,6 +235,9 @@ GstUri * gst_uri_new_with_base (GstUri * base,
GST_API
GstUri * gst_uri_from_string (const gchar * uri) G_GNUC_MALLOC;
GST_API
GstUri * gst_uri_from_string_escaped (const gchar * uri) G_GNUC_MALLOC;
GST_API
GstUri * gst_uri_from_string_with_base (GstUri * base,
const gchar * uri) G_GNUC_MALLOC;

View file

@ -336,6 +336,21 @@ struct URITest
{"scheme", "us:er:pa:ss", "hostname", 123, "/path", {{"query", NULL}, \
{NULL, NULL}}, "frag#ment"}},
#define ESCAPED_URI_TESTS \
/* Test cases for gst_uri_from_string_escaped */ \
{"scheme://user%20info@hostname", \
{"scheme", "user%20info", "hostname", GST_URI_NO_PORT, NULL, {{NULL, \
NULL}}, NULL}}, \
{"scheme://userinfo@hostname:123/path?query#frag%23ment", \
{"scheme", "userinfo", "hostname", 123, "/path", {{"query", NULL}, \
{NULL, NULL}}, "frag%23ment"}}, \
{"scheme://us%3Aer:pass@hostname", \
{"scheme", "us%3Aer:pass", "hostname", GST_URI_NO_PORT, NULL, {{NULL, \
NULL}}, NULL}}, \
{"scheme://us%3Aer:pa%3Ass@hostname:123/path?query#frag%23ment", \
{"scheme", "us%3Aer:pa%3Ass", "hostname", 123, "/path", {{"query", NULL}, \
{NULL, NULL}}, "frag%23ment"}},
static const struct URITest tests[] = {
COMMON_URI_TESTS UNESCAPED_URI_TESTS
@ -400,6 +415,63 @@ GST_START_TEST (test_url_parsing)
GST_END_TEST;
static const struct URITest escaped_tests[] = {
COMMON_URI_TESTS ESCAPED_URI_TESTS
};
GST_START_TEST (test_url_parsing_escaped)
{
GstUri *uri;
GList *list;
gchar *tmp_str;
guint i, j;
for (i = 0; i < G_N_ELEMENTS (escaped_tests); i++) {
GST_DEBUG ("Testing URI '%s'", escaped_tests[i].str);
uri = gst_uri_from_string_escaped (escaped_tests[i].str);
fail_unless (uri != NULL);
fail_unless_equals_string (gst_uri_get_scheme (uri),
escaped_tests[i].uri.scheme);
fail_unless_equals_string (gst_uri_get_userinfo (uri),
escaped_tests[i].uri.userinfo);
fail_unless_equals_string (gst_uri_get_host (uri),
escaped_tests[i].uri.host);
fail_unless_equals_int (gst_uri_get_port (uri), escaped_tests[i].uri.port);
tmp_str = gst_uri_get_path (uri);
fail_unless_equals_string (tmp_str, escaped_tests[i].uri.path);
g_free (tmp_str);
for (j = 0; j < 10; j++) {
if (!escaped_tests[i].uri.query[j].key)
break;
if (escaped_tests[i].uri.query[j].value) {
fail_unless_equals_string (gst_uri_get_query_value (uri,
escaped_tests[i].uri.query[j].key),
escaped_tests[i].uri.query[j].value);
} else {
fail_unless (gst_uri_query_has_key (uri,
escaped_tests[i].uri.query[j].key));
}
}
list = gst_uri_get_query_keys (uri);
fail_unless_equals_int (j, g_list_length (list));
g_list_free (list);
gst_uri_unref (uri);
}
for (i = 0; i < G_N_ELEMENTS (unparsable_uri_tests); i++) {
GST_DEBUG ("Testing unparsable URI '%s'", unparsable_uri_tests[i]);
uri = gst_uri_from_string (unparsable_uri_tests[i]);
fail_unless (uri == NULL);
}
}
GST_END_TEST;
static const struct URITest url_presenting_tests[] = {
/* check all URI elements present */
{.uri = {"scheme", "user:pass", "host", 1234, "/path/to/dir",
@ -1141,6 +1213,7 @@ gst_uri_suite (void)
tcase_add_test (tc_chain, test_win32_uri);
#endif
tcase_add_test (tc_chain, test_url_parsing);
tcase_add_test (tc_chain, test_url_parsing_escaped);
tcase_add_test (tc_chain, test_url_presenting);
tcase_add_test (tc_chain, test_url_normalization);
tcase_add_test (tc_chain, test_url_joining);