diff --git a/gst/gsturi.c b/gst/gsturi.c index 84f1141426..067629bb40 100644 --- a/gst/gsturi.c +++ b/gst/gsturi.c @@ -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. diff --git a/gst/gsturi.h b/gst/gsturi.h index 0cbeff367a..6f8afed7ea 100644 --- a/gst/gsturi.h +++ b/gst/gsturi.h @@ -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; diff --git a/tests/check/gst/gsturi.c b/tests/check/gst/gsturi.c index 8c304c5e61..3364ee7f42 100644 --- a/tests/check/gst/gsturi.c +++ b/tests/check/gst/gsturi.c @@ -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);