From 9f42c7b8dc1d099b1464070ca993189bf7a3cdd0 Mon Sep 17 00:00:00 2001 From: Carlos Garcia Campos Date: Mon, 26 Oct 2020 11:50:54 +0100 Subject: uri: add soup_uri_decode_data_uri to decode data URIs --- docs/reference/libsoup-3.0-sections.txt | 1 + libsoup/soup-uri.c | 78 +++++++++++++++++++++++++++++++++ libsoup/soup-uri.h | 3 ++ tests/uri-parsing-test.c | 48 ++++++++++---------- 4 files changed, 106 insertions(+), 24 deletions(-) diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt index a2d65cf3..6e664d92 100644 --- a/docs/reference/libsoup-3.0-sections.txt +++ b/docs/reference/libsoup-3.0-sections.txt @@ -594,6 +594,7 @@ soup_uri_free soup_uri_encode soup_uri_decode soup_uri_normalize +soup_uri_decode_data_uri SOUP_URI_SCHEME_HTTP SOUP_URI_SCHEME_HTTPS diff --git a/libsoup/soup-uri.c b/libsoup/soup-uri.c index fd8dc828..2ad744be 100644 --- a/libsoup/soup-uri.c +++ b/libsoup/soup-uri.c @@ -1381,4 +1381,82 @@ soup_uri_is_https (SoupURI *uri, char **aliases) return FALSE; } +#define BASE64_INDICATOR ";base64" +#define BASE64_INDICATOR_LEN (sizeof (";base64") - 1) + +/** + * soup_uri_decode_data: + * @uri: a data URI, in string form + * @content_type: (out) (nullable) (transfer full): location to store content type, or %NULL + * + * Decodes the given data URI and returns its contents and @content_type. + * + * Returns: (transfer full): a #GBytes with the contents of @uri, + * or %NULL if @uri is not a valid data URI + */ +GBytes * +soup_uri_decode_data_uri (const char *uri, + char **content_type) +{ + SoupURI *soup_uri; + const char *comma, *start, *end; + gboolean base64 = FALSE; + char *uri_string; + GBytes *bytes; + + g_return_val_if_fail (uri != NULL, NULL); + + soup_uri = soup_uri_new (uri); + if (!soup_uri) + return NULL; + + if (soup_uri->scheme != SOUP_URI_SCHEME_DATA || soup_uri->host != NULL) + return NULL; + + if (content_type) + *content_type = NULL; + + uri_string = soup_uri_to_string (soup_uri, FALSE); + soup_uri_free (soup_uri); + + start = uri_string + 5; + comma = strchr (start, ','); + if (comma && comma != start) { + /* Deal with MIME type / params */ + if (comma >= start + BASE64_INDICATOR_LEN && !g_ascii_strncasecmp (comma - BASE64_INDICATOR_LEN, BASE64_INDICATOR, BASE64_INDICATOR_LEN)) { + end = comma - BASE64_INDICATOR_LEN; + base64 = TRUE; + } else + end = comma; + + if (end != start && content_type) + *content_type = soup_uri_decoded_copy (start, end - start, NULL); + } + + if (content_type && !*content_type) + *content_type = g_strdup ("text/plain;charset=US-ASCII"); + + if (comma) + start = comma + 1; + + if (*start) { + gsize content_length; + int decoded_length = 0; + guchar *buffer = (guchar *) soup_uri_decoded_copy (start, strlen (start), + &decoded_length); + + if (base64) + buffer = g_base64_decode_inplace ((gchar*)buffer, &content_length); + else + content_length = decoded_length; + + bytes = g_bytes_new_take (buffer, content_length); + } else { + bytes = g_bytes_new_static (NULL, 0); + } + g_free (uri_string); + + return bytes; +} + G_DEFINE_BOXED_TYPE (SoupURI, soup_uri, soup_uri_copy, soup_uri_free) diff --git a/libsoup/soup-uri.h b/libsoup/soup-uri.h index 6e949ea1..ea24a53c 100644 --- a/libsoup/soup-uri.h +++ b/libsoup/soup-uri.h @@ -72,6 +72,9 @@ char *soup_uri_decode (const char *part); SOUP_AVAILABLE_IN_2_4 char *soup_uri_normalize (const char *part, const char *unescape_extra); +SOUP_AVAILABLE_IN_ALL +GBytes *soup_uri_decode_data_uri (const char *uri, + char **content_type); SOUP_AVAILABLE_IN_2_4 gboolean soup_uri_uses_default_port (SoupURI *uri); diff --git a/tests/uri-parsing-test.c b/tests/uri-parsing-test.c index d463f1f4..9acadf7c 100644 --- a/tests/uri-parsing-test.c +++ b/tests/uri-parsing-test.c @@ -525,43 +525,43 @@ static const DataURITest data_tests[] = { "" }, { "data:text/plain,", "text/plain", - "" } + "" }, + { "data://text/plain,foo", + NULL, + NULL }, + { "http:text/plain,foo%20bar", + NULL, + NULL }, + { "./foo", + NULL, + NULL }, }; static void do_data_tests (void) { - SoupSession *session; - SoupRequest *req; - GInputStream *stream; - char buf[128]; - gsize nread; int i; - GError *error = NULL; - session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); for (i = 0; i < G_N_ELEMENTS (data_tests); i++) { - req = soup_session_request (session, data_tests[i].uri, &error); - g_assert_no_error (error); - - stream = soup_request_send (req, NULL, &error); - g_assert_no_error (error); + GBytes *bytes; + char *content_type = NULL; - g_input_stream_read_all (stream, buf, sizeof (buf), &nread, NULL, &error); + bytes = soup_uri_decode_data_uri (data_tests[i].uri, &content_type); + if (!data_tests[i].body) { + g_assert_null (bytes); + g_assert_null (content_type); - g_assert_no_error (error); - g_assert_cmpint (nread, ==, strlen (data_tests[i].body)); - buf[nread] = 0; - g_assert_cmpstr (buf, ==, data_tests[i].body); + continue; + } - g_assert_cmpstr (soup_request_get_content_type (req), ==, data_tests[i].mime_type); + g_assert_nonnull (bytes); + g_assert_cmpmem (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), + data_tests[i].body, strlen (data_tests[i].body)); + g_assert_cmpstr (content_type, ==, data_tests[i].mime_type); - g_input_stream_close (stream, NULL, &error); - g_assert_no_error (error); - g_object_unref (stream); - g_object_unref (req); + g_free (content_type); + g_bytes_unref (bytes); } - soup_test_session_abort_unref (session); } static void -- cgit v1.2.1