mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 21:22:46 +00:00 
			
		
		
		
	AK: Decode data URLs to separate class (and parse like every other URL)
Parsing 'data:' URLs took it's own route. It never set standard URL
fields like path, query or fragment (except for scheme) and instead
gave us separate methods called `data_payload()`, `data_mime_type()`,
and `data_payload_is_base64()`.
Because parsing 'data:' didn't use standard fields, running the
following JS code:
    new URL('#a', 'data:text/plain,hello').toString()
not only cleared the path as URLParser doesn't check for data from
data_payload() function (making the result be 'data:#a'), but it also
crashes the program because we forbid having an empty MIME type when we
serialize to string.
With this change, 'data:' URLs will be parsed like every other URLs.
To decode the 'data:' URL contents, one needs to call process_data_url()
on a URL, which will return a struct containing MIME type with already
decoded data! :^)
			
			
This commit is contained in:
		
							parent
							
								
									f27b9b9563
								
							
						
					
					
						commit
						eb41f0144b
					
				
					 9 changed files with 172 additions and 165 deletions
				
			
		
							
								
								
									
										128
									
								
								AK/URL.cpp
									
										
									
									
									
								
							
							
						
						
									
										128
									
								
								AK/URL.cpp
									
										
									
									
									
								
							|  | @ -5,6 +5,7 @@ | |||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #include <AK/Base64.h> | ||||
| #include <AK/CharacterTypes.h> | ||||
| #include <AK/Debug.h> | ||||
| #include <AK/LexicalPath.h> | ||||
|  | @ -173,18 +174,7 @@ bool URL::compute_validity() const | |||
|     if (m_scheme.is_empty()) | ||||
|         return false; | ||||
| 
 | ||||
|     if (m_scheme == "data") { | ||||
|         if (m_data_mime_type.is_empty()) | ||||
|             return false; | ||||
|         if (m_data_payload_is_base64) { | ||||
|             if (m_data_payload.length() % 4 != 0) | ||||
|                 return false; | ||||
|             for (auto character : m_data_payload) { | ||||
|                 if (!is_ascii_alphanumeric(character) || character == '+' || character == '/' || character == '=') | ||||
|                     return false; | ||||
|             } | ||||
|         } | ||||
|     } else if (m_cannot_be_a_base_url) { | ||||
|     if (m_cannot_be_a_base_url) { | ||||
|         if (m_paths.size() != 1) | ||||
|             return false; | ||||
|         if (m_paths[0].is_empty()) | ||||
|  | @ -275,6 +265,22 @@ URL URL::create_with_url_or_path(DeprecatedString const& url_or_path) | |||
|     return URL::create_with_file_scheme(path); | ||||
| } | ||||
| 
 | ||||
| URL URL::create_with_data(StringView mime_type, StringView payload, bool is_base64) | ||||
| { | ||||
|     URL url; | ||||
|     url.set_cannot_be_a_base_url(true); | ||||
|     url.set_scheme("data"sv); | ||||
| 
 | ||||
|     StringBuilder builder; | ||||
|     builder.append(mime_type); | ||||
|     if (is_base64) | ||||
|         builder.append(";base64"sv); | ||||
|     builder.append(','); | ||||
|     builder.append(payload); | ||||
|     url.set_paths({ builder.to_deprecated_string() }); | ||||
|     return url; | ||||
| } | ||||
| 
 | ||||
| // https://url.spec.whatwg.org/#special-scheme
 | ||||
| bool URL::is_special_scheme(StringView scheme) | ||||
| { | ||||
|  | @ -293,30 +299,9 @@ DeprecatedString URL::serialize_path(ApplyPercentDecoding apply_percent_decoding | |||
|     return builder.to_deprecated_string(); | ||||
| } | ||||
| 
 | ||||
| DeprecatedString URL::serialize_data_url() const | ||||
| { | ||||
|     VERIFY(m_scheme == "data"); | ||||
|     VERIFY(!m_data_mime_type.is_null()); | ||||
|     VERIFY(!m_data_payload.is_null()); | ||||
|     StringBuilder builder; | ||||
|     builder.append(m_scheme); | ||||
|     builder.append(':'); | ||||
|     builder.append(m_data_mime_type); | ||||
|     if (m_data_payload_is_base64) | ||||
|         builder.append(";base64"sv); | ||||
|     builder.append(','); | ||||
|     // NOTE: The specification does not say anything about encoding this, but we should encode at least control and non-ASCII
 | ||||
|     //       characters (since this is also a valid representation of the same data URL).
 | ||||
|     builder.append(URL::percent_encode(m_data_payload, PercentEncodeSet::C0Control)); | ||||
|     return builder.to_deprecated_string(); | ||||
| } | ||||
| 
 | ||||
| // https://url.spec.whatwg.org/#concept-url-serializer
 | ||||
| DeprecatedString URL::serialize(ExcludeFragment exclude_fragment) const | ||||
| { | ||||
|     if (m_scheme == "data") | ||||
|         return serialize_data_url(); | ||||
| 
 | ||||
|     // 1. Let output be url’s scheme and U+003A (:) concatenated.
 | ||||
|     StringBuilder output; | ||||
|     output.append(m_scheme); | ||||
|  | @ -387,8 +372,7 @@ DeprecatedString URL::serialize(ExcludeFragment exclude_fragment) const | |||
| DeprecatedString URL::serialize_for_display() const | ||||
| { | ||||
|     VERIFY(m_valid); | ||||
|     if (m_scheme == "data") | ||||
|         return serialize_data_url(); | ||||
| 
 | ||||
|     StringBuilder builder; | ||||
|     builder.append(m_scheme); | ||||
|     builder.append(':'); | ||||
|  | @ -466,6 +450,80 @@ bool URL::equals(URL const& other, ExcludeFragment exclude_fragments) const | |||
|     return serialize(exclude_fragments) == other.serialize(exclude_fragments); | ||||
| } | ||||
| 
 | ||||
| // https://fetch.spec.whatwg.org/#data-url-processor
 | ||||
| ErrorOr<URL::DataURL> URL::process_data_url() const | ||||
| { | ||||
|     // 1. Assert: dataURL’s scheme is "data".
 | ||||
|     VERIFY(scheme() == "data"); | ||||
| 
 | ||||
|     // 2. Let input be the result of running the URL serializer on dataURL with exclude fragment set to true.
 | ||||
|     auto input = serialize(URL::ExcludeFragment::Yes); | ||||
| 
 | ||||
|     // 3. Remove the leading "data:" from input.
 | ||||
|     input = input.substring("data:"sv.length()); | ||||
| 
 | ||||
|     // 4. Let position point at the start of input.
 | ||||
| 
 | ||||
|     // 5. Let mimeType be the result of collecting a sequence of code points that are not equal to U+002C (,), given position.
 | ||||
|     auto position = input.find(','); | ||||
|     auto mime_type = input.substring_view(0, position.value_or(input.length())); | ||||
| 
 | ||||
|     // 6. Strip leading and trailing ASCII whitespace from mimeType.
 | ||||
|     mime_type = mime_type.trim_whitespace(TrimMode::Both); | ||||
| 
 | ||||
|     // 7. If position is past the end of input, then return failure.
 | ||||
|     if (!position.has_value()) | ||||
|         return Error::from_string_literal("Missing a comma character"); | ||||
| 
 | ||||
|     // 8. Advance position by 1.
 | ||||
|     position = position.value() + 1; | ||||
| 
 | ||||
|     // 9. Let encodedBody be the remainder of input.
 | ||||
|     auto encoded_body = input.substring_view(position.value()); | ||||
| 
 | ||||
|     // 10. Let body be the percent-decoding of encodedBody.
 | ||||
|     auto body = URL::percent_decode(encoded_body).to_byte_buffer(); | ||||
| 
 | ||||
|     // 11. If mimeType ends with U+003B (;), followed by zero or more U+0020 SPACE, followed by an ASCII case-insensitive match for "base64", then:
 | ||||
|     if (mime_type.ends_with("base64"sv, CaseSensitivity::CaseInsensitive)) { | ||||
|         auto trimmed_substring_view = mime_type.substring_view(0, mime_type.length() - 6); | ||||
|         trimmed_substring_view = trimmed_substring_view.trim(" "sv, TrimMode::Right); | ||||
|         if (trimmed_substring_view.ends_with(';')) { | ||||
|             // 1. Let stringBody be the isomorphic decode of body.
 | ||||
|             auto string_body = StringView(body); | ||||
| 
 | ||||
|             // 2. Set body to the forgiving-base64 decode of stringBody.
 | ||||
|             //    FIXME: Check if it's really forgiving.
 | ||||
|             // 3. If body is failure, then return failure.
 | ||||
|             body = TRY(decode_base64(string_body)); | ||||
| 
 | ||||
|             // 4. Remove the last 6 code points from mimeType.
 | ||||
|             // 5. Remove trailing U+0020 SPACE code points from mimeType, if any.
 | ||||
|             // 6. Remove the last U+003B (;) from mimeType.
 | ||||
|             mime_type = trimmed_substring_view.substring_view(0, trimmed_substring_view.length() - 1); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // 12. If mimeType starts with ";", then prepend "text/plain" to mimeType.
 | ||||
|     StringBuilder builder; | ||||
|     if (mime_type.starts_with(';')) { | ||||
|         builder.append("text/plain"sv); | ||||
|         builder.append(mime_type); | ||||
|         mime_type = builder.string_view(); | ||||
|     } | ||||
| 
 | ||||
|     // FIXME: Parse the MIME type's components according to https://mimesniff.spec.whatwg.org/#parse-a-mime-type
 | ||||
|     // FIXME: 13. Let mimeTypeRecord be the result of parsing mimeType.
 | ||||
|     auto mime_type_record = mime_type.trim("\n\r\t "sv, TrimMode::Both); | ||||
| 
 | ||||
|     // 14. If mimeTypeRecord is failure, then set mimeTypeRecord to text/plain;charset=US-ASCII.
 | ||||
|     if (mime_type_record.is_empty()) | ||||
|         mime_type_record = "text/plain;charset=US-ASCII"sv; | ||||
| 
 | ||||
|     // 15. Return a new data: URL struct whose MIME type is mimeTypeRecord and body is body.
 | ||||
|     return URL::DataURL { TRY(String::from_utf8(mime_type_record)), body }; | ||||
| } | ||||
| 
 | ||||
| void URL::append_percent_encoded(StringBuilder& builder, u32 code_point) | ||||
| { | ||||
|     if (code_point <= 0x7f) | ||||
|  |  | |||
							
								
								
									
										24
									
								
								AK/URL.h
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								AK/URL.h
									
										
									
									
									
								
							|  | @ -128,14 +128,16 @@ public: | |||
| 
 | ||||
|     URL complete_url(StringView) const; | ||||
| 
 | ||||
|     bool data_payload_is_base64() const { return m_data_payload_is_base64; } | ||||
|     DeprecatedString const& data_mime_type() const { return m_data_mime_type; } | ||||
|     DeprecatedString const& data_payload() const { return m_data_payload; } | ||||
|     struct DataURL { | ||||
|         String mime_type; | ||||
|         ByteBuffer body; | ||||
|     }; | ||||
|     ErrorOr<DataURL> process_data_url() const; | ||||
| 
 | ||||
|     static URL create_with_url_or_path(DeprecatedString const&); | ||||
|     static URL create_with_file_scheme(DeprecatedString const& path, DeprecatedString const& fragment = {}, DeprecatedString const& hostname = {}); | ||||
|     static URL create_with_help_scheme(DeprecatedString const& path, DeprecatedString const& fragment = {}, DeprecatedString const& hostname = {}); | ||||
|     static URL create_with_data(DeprecatedString mime_type, DeprecatedString payload, bool is_base64 = false) { return URL(move(mime_type), move(payload), is_base64); } | ||||
|     static URL create_with_data(StringView mime_type, StringView payload, bool is_base64 = false); | ||||
| 
 | ||||
|     static u16 default_port_for_scheme(StringView); | ||||
|     static bool is_special_scheme(StringView); | ||||
|  | @ -152,17 +154,7 @@ public: | |||
|     static bool code_point_is_in_percent_encode_set(u32 code_point, URL::PercentEncodeSet); | ||||
| 
 | ||||
| private: | ||||
|     URL(DeprecatedString&& data_mime_type, DeprecatedString&& data_payload, bool payload_is_base64) | ||||
|         : m_valid(true) | ||||
|         , m_scheme("data") | ||||
|         , m_data_payload_is_base64(payload_is_base64) | ||||
|         , m_data_mime_type(move(data_mime_type)) | ||||
|         , m_data_payload(move(data_payload)) | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     bool compute_validity() const; | ||||
|     DeprecatedString serialize_data_url() const; | ||||
| 
 | ||||
|     static void append_percent_encoded_if_necessary(StringBuilder&, u32 code_point, PercentEncodeSet set = PercentEncodeSet::Userinfo); | ||||
|     static void append_percent_encoded(StringBuilder&, u32 code_point); | ||||
|  | @ -196,10 +188,6 @@ private: | |||
|     DeprecatedString m_fragment; | ||||
| 
 | ||||
|     bool m_cannot_be_a_base_url { false }; | ||||
| 
 | ||||
|     bool m_data_payload_is_base64 { false }; | ||||
|     DeprecatedString m_data_mime_type; | ||||
|     DeprecatedString m_data_payload; | ||||
| }; | ||||
| 
 | ||||
| template<> | ||||
|  |  | |||
|  | @ -697,42 +697,6 @@ DeprecatedString URLParser::percent_encode_after_encoding(StringView input, URL: | |||
|     return output.to_deprecated_string(); | ||||
| } | ||||
| 
 | ||||
| // https://fetch.spec.whatwg.org/#data-urls
 | ||||
| // FIXME: This only loosely follows the spec, as we use the same class for "regular" and data URLs, unlike the spec.
 | ||||
| Optional<URL> URLParser::parse_data_url(StringView raw_input) | ||||
| { | ||||
|     dbgln_if(URL_PARSER_DEBUG, "URLParser::parse_data_url: Parsing '{}'.", raw_input); | ||||
|     VERIFY(raw_input.starts_with("data:"sv)); | ||||
|     auto input = raw_input.substring_view(5); | ||||
|     auto comma_offset = input.find(','); | ||||
|     if (!comma_offset.has_value()) | ||||
|         return {}; | ||||
|     auto mime_type = StringUtils::trim(input.substring_view(0, comma_offset.value()), "\t\n\f\r "sv, TrimMode::Both); | ||||
|     auto encoded_body = input.substring_view(comma_offset.value() + 1); | ||||
|     auto body = URL::percent_decode(encoded_body); | ||||
|     bool is_base64_encoded = false; | ||||
|     if (mime_type.ends_with("base64"sv, CaseSensitivity::CaseInsensitive)) { | ||||
|         auto substring_view = mime_type.substring_view(0, mime_type.length() - 6); | ||||
|         auto trimmed_substring_view = StringUtils::trim(substring_view, " "sv, TrimMode::Right); | ||||
|         if (trimmed_substring_view.ends_with(';')) { | ||||
|             is_base64_encoded = true; | ||||
|             mime_type = trimmed_substring_view.substring_view(0, trimmed_substring_view.length() - 1); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     StringBuilder builder; | ||||
|     if (mime_type.starts_with(";"sv) || mime_type.is_empty()) { | ||||
|         builder.append("text/plain"sv); | ||||
|         builder.append(mime_type); | ||||
|         mime_type = builder.string_view(); | ||||
|     } | ||||
| 
 | ||||
|     // FIXME: Parse the MIME type's components according to https://mimesniff.spec.whatwg.org/#parse-a-mime-type
 | ||||
|     URL url { StringUtils::trim(mime_type, "\n\r\t "sv, TrimMode::Both), move(body), is_base64_encoded }; | ||||
|     dbgln_if(URL_PARSER_DEBUG, "URLParser::parse_data_url: Parsed data URL to be '{}'.", url.serialize()); | ||||
|     return url; | ||||
| } | ||||
| 
 | ||||
| // https://url.spec.whatwg.org/#concept-basic-url-parser
 | ||||
| // NOTE: This parser assumes a UTF-8 encoding.
 | ||||
| // NOTE: Refrain from using the URL classes setters inside this algorithm. Rather, set the values directly. This bypasses the setters' built-in
 | ||||
|  | @ -746,13 +710,6 @@ URL URLParser::basic_parse(StringView raw_input, Optional<URL> const& base_url, | |||
|     if (raw_input.is_empty()) | ||||
|         return base_url.has_value() ? *base_url : URL {}; | ||||
| 
 | ||||
|     if (raw_input.starts_with("data:"sv)) { | ||||
|         auto maybe_url = parse_data_url(raw_input); | ||||
|         if (!maybe_url.has_value()) | ||||
|             return {}; | ||||
|         return maybe_url.release_value(); | ||||
|     } | ||||
| 
 | ||||
|     size_t start_index = 0; | ||||
|     size_t end_index = raw_input.length(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -63,9 +63,6 @@ public: | |||
| 
 | ||||
|     // https://url.spec.whatwg.org/#concept-host-serializer
 | ||||
|     static ErrorOr<String> serialize_host(URL::Host const&); | ||||
| 
 | ||||
| private: | ||||
|     static Optional<URL> parse_data_url(StringView raw_input); | ||||
| }; | ||||
| 
 | ||||
| #undef ENUMERATE_STATES | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ | |||
| 
 | ||||
| #include <LibTest/TestCase.h> | ||||
| 
 | ||||
| #include <AK/Base64.h> | ||||
| #include <AK/URL.h> | ||||
| #include <AK/URLParser.h> | ||||
| 
 | ||||
|  | @ -111,7 +110,6 @@ TEST_CASE(some_bad_urls) | |||
|     EXPECT_EQ(URL("http://serenityos.org:abc"sv).is_valid(), false); | ||||
|     EXPECT_EQ(URL("http://serenityos.org:abc:80"sv).is_valid(), false); | ||||
|     EXPECT_EQ(URL("http://serenityos.org:abc:80/"sv).is_valid(), false); | ||||
|     EXPECT_EQ(URL("data:"sv).is_valid(), false); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(serialization) | ||||
|  | @ -246,10 +244,11 @@ TEST_CASE(data_url) | |||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
|     EXPECT_EQ(url.data_mime_type(), "text/html"); | ||||
|     EXPECT_EQ(url.data_payload(), "test"); | ||||
|     EXPECT(!url.data_payload_is_base64()); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/html,test"); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/html"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "test"sv); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(data_url_default_mime_type) | ||||
|  | @ -258,10 +257,11 @@ TEST_CASE(data_url_default_mime_type) | |||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
|     EXPECT_EQ(url.data_mime_type(), "text/plain"); | ||||
|     EXPECT_EQ(url.data_payload(), "test"); | ||||
|     EXPECT(!url.data_payload_is_base64()); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/plain,test"); | ||||
|     EXPECT_EQ(url.serialize(), "data:,test"); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/plain;charset=US-ASCII"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "test"sv); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(data_url_encoded) | ||||
|  | @ -270,46 +270,50 @@ TEST_CASE(data_url_encoded) | |||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
|     EXPECT_EQ(url.data_mime_type(), "text/html"); | ||||
|     EXPECT_EQ(url.data_payload(), "Hello friends,%0X%X0"); | ||||
|     EXPECT(!url.data_payload_is_base64()); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/html,Hello friends,%0X%X0"); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/html,Hello%20friends%2C%0X%X0"); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/html"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "Hello friends,%0X%X0"sv); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(data_url_base64_encoded) | ||||
| { | ||||
|     URL url("data:text/html;base64,test"sv); | ||||
|     URL url("data:text/html;base64,dGVzdA=="sv); | ||||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
|     EXPECT_EQ(url.data_mime_type(), "text/html"); | ||||
|     EXPECT_EQ(url.data_payload(), "test"); | ||||
|     EXPECT(url.data_payload_is_base64()); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/html;base64,test"); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/html;base64,dGVzdA=="); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/html"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "test"sv); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(data_url_base64_encoded_default_mime_type) | ||||
| { | ||||
|     URL url("data:;base64,test"sv); | ||||
|     URL url("data:;base64,dGVzdA=="sv); | ||||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
|     EXPECT_EQ(url.data_mime_type(), "text/plain"); | ||||
|     EXPECT_EQ(url.data_payload(), "test"); | ||||
|     EXPECT(url.data_payload_is_base64()); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/plain;base64,test"); | ||||
|     EXPECT_EQ(url.serialize(), "data:;base64,dGVzdA=="); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/plain;charset=US-ASCII"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "test"sv); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(data_url_base64_encoded_with_whitespace) | ||||
| { | ||||
|     URL url("data: text/html ;     bAsE64 , test with whitespace "sv); | ||||
|     URL url("data: text/html ;     bAsE64 , dGVz dA== "sv); | ||||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
|     EXPECT_EQ(url.data_mime_type(), "text/html"); | ||||
|     EXPECT_EQ(url.data_payload(), " test with whitespace "); | ||||
|     EXPECT(url.data_payload_is_base64()); | ||||
|     EXPECT_EQ(url.serialize(), "data:text/html;base64, test with whitespace "); | ||||
|     EXPECT_EQ(url.serialize(), "data: text/html ;     bAsE64 , dGVz dA=="); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/html"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "test"); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(data_url_base64_encoded_with_inline_whitespace) | ||||
|  | @ -318,12 +322,23 @@ TEST_CASE(data_url_base64_encoded_with_inline_whitespace) | |||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
|     EXPECT_EQ(url.data_mime_type(), "text/javascript"); | ||||
|     EXPECT(url.data_payload_is_base64()); | ||||
|     EXPECT_EQ(url.data_payload(), " ZD Qg\r\nPS An Zm91cic\r\n 7 "sv); | ||||
|     auto decode_result = decode_base64(url.data_payload()); | ||||
|     EXPECT_EQ(decode_result.is_error(), false); | ||||
|     EXPECT_EQ(StringView(decode_result.value()), "d4 = 'four';"sv); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/javascript"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "d4 = 'four';"sv); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(data_url_completed_with_fragment) | ||||
| { | ||||
|     auto url = URL("data:text/plain,test"sv).complete_url("#a"sv); | ||||
|     EXPECT(url.is_valid()); | ||||
|     EXPECT_EQ(url.scheme(), "data"); | ||||
|     EXPECT_EQ(url.fragment(), "a"); | ||||
|     EXPECT(url.host().has<Empty>()); | ||||
| 
 | ||||
|     auto data_url = TRY_OR_FAIL(url.process_data_url()); | ||||
|     EXPECT_EQ(data_url.mime_type, "text/plain"); | ||||
|     EXPECT_EQ(StringView(data_url.body.bytes()), "test"sv); | ||||
| } | ||||
| 
 | ||||
| TEST_CASE(trailing_slash_with_complete_url) | ||||
|  |  | |||
|  | @ -124,7 +124,7 @@ HelpWindow::HelpWindow(GUI::Window* parent) | |||
|             window->show(); | ||||
|         } else if (url.host() == String::from_utf8_short_string("doc"sv)) { | ||||
|             auto entry = LexicalPath::basename(url.serialize_path()); | ||||
|             m_webview->load(URL::create_with_data("text/html", render(entry))); | ||||
|             m_webview->load(URL::create_with_data("text/html"sv, render(entry))); | ||||
|         } else { | ||||
|             dbgln("Invalid spreadsheet action domain '{}'", url.serialized_host().release_value_but_fixme_should_propagate_errors()); | ||||
|         } | ||||
|  | @ -135,7 +135,7 @@ HelpWindow::HelpWindow(GUI::Window* parent) | |||
|             return; | ||||
| 
 | ||||
|         auto key = static_cast<HelpListModel*>(m_listview->model())->key(index); | ||||
|         m_webview->load(URL::create_with_data("text/html", render(key))); | ||||
|         m_webview->load(URL::create_with_data("text/html"sv, render(key))); | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -714,17 +714,15 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<PendingResponse>> scheme_fetch(JS::Realm& r | |||
|     // -> "data"
 | ||||
|     else if (request->current_url().scheme() == "data"sv) { | ||||
|         // 1. Let dataURLStruct be the result of running the data: URL processor on request’s current URL.
 | ||||
|         auto const& url = request->current_url(); | ||||
|         auto data_or_error = url.data_payload_is_base64() | ||||
|             ? decode_base64(url.data_payload()) | ||||
|             : TRY_OR_THROW_OOM(vm, ByteBuffer::copy(url.data_payload().bytes())); | ||||
|         auto data_url_struct = request->current_url().process_data_url(); | ||||
| 
 | ||||
|         // 2. If dataURLStruct is failure, then return a network error.
 | ||||
|         if (data_or_error.is_error()) | ||||
|             return PendingResponse::create(vm, request, Infrastructure::Response::network_error(vm, "Request has invalid base64 'data:' URL"sv)); | ||||
|         if (data_url_struct.is_error()) | ||||
|             return PendingResponse::create(vm, request, Infrastructure::Response::network_error(vm, "Failed to process 'data:' URL"sv)); | ||||
| 
 | ||||
|         // 3. Let mimeType be dataURLStruct’s MIME type, serialized.
 | ||||
|         auto const& mime_type = url.data_mime_type(); | ||||
|         //    FIXME: Serialize MIME type.
 | ||||
|         auto const& mime_type = data_url_struct.value().mime_type; | ||||
| 
 | ||||
|         // 4. Return a new response whose status message is `OK`, header list is « (`Content-Type`, mimeType) », and
 | ||||
|         //    body is dataURLStruct’s body as a body.
 | ||||
|  | @ -732,7 +730,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<PendingResponse>> scheme_fetch(JS::Realm& r | |||
|         response->set_status_message(MUST(ByteBuffer::copy("OK"sv.bytes()))); | ||||
|         auto header = TRY_OR_THROW_OOM(vm, Infrastructure::Header::from_string_pair("Content-Type"sv, mime_type)); | ||||
|         TRY_OR_THROW_OOM(vm, response->header_list()->append(move(header))); | ||||
|         response->set_body(TRY(Infrastructure::byte_sequence_as_body(realm, data_or_error.value().span()))); | ||||
|         response->set_body(TRY(Infrastructure::byte_sequence_as_body(realm, data_url_struct.value().body))); | ||||
|         return PendingResponse::create(vm, request, response); | ||||
|     } | ||||
|     // -> "file"
 | ||||
|  |  | |||
|  | @ -195,7 +195,7 @@ DeprecatedString HTMLCanvasElement::to_data_url(DeprecatedString const& type, [[ | |||
|         // FIXME: propagate error
 | ||||
|         return {}; | ||||
|     } | ||||
|     return AK::URL::create_with_data(type, base64_encoded_or_error.release_value().to_deprecated_string(), true).to_deprecated_string(); | ||||
|     return AK::URL::create_with_data(type, base64_encoded_or_error.release_value(), true).to_deprecated_string(); | ||||
| } | ||||
| 
 | ||||
| void HTMLCanvasElement::present() | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ | |||
|  * SPDX-License-Identifier: BSD-2-Clause | ||||
|  */ | ||||
| 
 | ||||
| #include <AK/Base64.h> | ||||
| #include <AK/Debug.h> | ||||
| #include <AK/JsonObject.h> | ||||
| #include <LibCore/ElapsedTimer.h> | ||||
|  | @ -123,7 +122,7 @@ RefPtr<Resource> ResourceLoader::load_resource(Resource::Type type, LoadRequest& | |||
| static DeprecatedString sanitized_url_for_logging(AK::URL const& url) | ||||
| { | ||||
|     if (url.scheme() == "data"sv) | ||||
|         return DeprecatedString::formatted("[data URL, mime-type={}, size={}]", url.data_mime_type(), url.data_payload().length()); | ||||
|         return "[data URL]"sv; | ||||
|     return url.to_deprecated_string(); | ||||
| } | ||||
| 
 | ||||
|  | @ -204,30 +203,25 @@ void ResourceLoader::load(LoadRequest& request, Function<void(ReadonlyBytes, Has | |||
|     } | ||||
| 
 | ||||
|     if (url.scheme() == "data") { | ||||
|         dbgln_if(SPAM_DEBUG, "ResourceLoader loading a data URL with mime-type: '{}', base64={}, payload='{}'", | ||||
|             url.data_mime_type(), | ||||
|             url.data_payload_is_base64(), | ||||
|             url.data_payload()); | ||||
| 
 | ||||
|         ByteBuffer data; | ||||
|         if (url.data_payload_is_base64()) { | ||||
|             auto data_maybe = decode_base64(url.data_payload()); | ||||
|             if (data_maybe.is_error()) { | ||||
|                 auto error_message = data_maybe.error().string_literal(); | ||||
|                 log_failure(request, error_message); | ||||
|                 error_callback(error_message, {}); | ||||
|                 return; | ||||
|             } | ||||
|             data = data_maybe.value(); | ||||
|         } else { | ||||
|             data = url.data_payload().to_byte_buffer(); | ||||
|         auto data_url_or_error = url.process_data_url(); | ||||
|         if (data_url_or_error.is_error()) { | ||||
|             auto error_message = data_url_or_error.error().string_literal(); | ||||
|             log_failure(request, error_message); | ||||
|             error_callback(error_message, {}); | ||||
|             return; | ||||
|         } | ||||
|         auto data_url = data_url_or_error.release_value(); | ||||
| 
 | ||||
|         dbgln_if(SPAM_DEBUG, "ResourceLoader loading a data URL with mime-type: '{}', payload='{}'", | ||||
|             data_url.mime_type, | ||||
|             StringView(data_url.body.bytes())); | ||||
| 
 | ||||
|         HashMap<DeprecatedString, DeprecatedString, CaseInsensitiveStringTraits> response_headers; | ||||
|         response_headers.set("Content-Type", url.data_mime_type()); | ||||
|         response_headers.set("Content-Type", data_url.mime_type.to_deprecated_string()); | ||||
| 
 | ||||
|         log_success(request); | ||||
|         Platform::EventLoopPlugin::the().deferred_invoke([data = move(data), response_headers = move(response_headers), success_callback = move(success_callback)] { | ||||
| 
 | ||||
|         Platform::EventLoopPlugin::the().deferred_invoke([data = move(data_url.body), response_headers = move(response_headers), success_callback = move(success_callback)] { | ||||
|             success_callback(data, response_headers, {}); | ||||
|         }); | ||||
|         return; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Karol Kosek
						Karol Kosek