From 45a3b61c351a12bd9cbba5f06423bc5657377621 Mon Sep 17 00:00:00 2001 From: Feng Yu Date: Fri, 6 Dec 2024 09:35:02 -0800 Subject: [PATCH] LibWeb: Ensure Headers API can handle non-ascii characters This patch ensure Headers object's associated header list is ISO-8859-1 encoded when set using `Infra::isomorphic_encode`, and correctly decoded using `Infra::isomorphic_decode`. Follow-up of https://github.com/LadybirdBrowser/ladybird/pull/1893 --- Libraries/LibWeb/Fetch/Headers.cpp | 17 ++--- Libraries/LibWeb/Fetch/HeadersIterator.cpp | 5 +- .../Fetch/fetch-headers-non-ascii.txt | 34 ++++++++++ .../api/headers/headers-normalize.any.txt | 8 +++ .../input/Fetch/fetch-headers-non-ascii.html | 66 +++++++++++++++++++ .../api/headers/headers-normalize.any.html | 15 +++++ .../api/headers/headers-normalize.any.js | 56 ++++++++++++++++ 7 files changed, 188 insertions(+), 13 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt create mode 100644 Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js diff --git a/Libraries/LibWeb/Fetch/Headers.cpp b/Libraries/LibWeb/Fetch/Headers.cpp index 1d4dbf31a7ec..b60503d7c270 100644 --- a/Libraries/LibWeb/Fetch/Headers.cpp +++ b/Libraries/LibWeb/Fetch/Headers.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::Fetch { @@ -56,10 +57,7 @@ void Headers::visit_edges(JS::Cell::Visitor& visitor) WebIDL::ExceptionOr Headers::append(String const& name_string, String const& value_string) { // The append(name, value) method steps are to append (name, value) to this. - auto header = Infrastructure::Header { - .name = MUST(ByteBuffer::copy(name_string.bytes())), - .value = MUST(ByteBuffer::copy(value_string.bytes())), - }; + auto header = Infrastructure::Header::from_string_pair(name_string, value_string); TRY(append(move(header))); return {}; } @@ -106,7 +104,7 @@ WebIDL::ExceptionOr> Headers::get(String const& name_string) // 2. Return the result of getting name from this’s header list. auto byte_buffer = m_header_list->get(name); - return byte_buffer.has_value() ? MUST(String::from_utf8(*byte_buffer)) : Optional {}; + return byte_buffer.has_value() ? Infra::isomorphic_decode(*byte_buffer) : Optional {}; } // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie @@ -123,7 +121,7 @@ Vector Headers::get_set_cookie() // `Set-Cookie`, in order. for (auto const& header : *m_header_list) { if (StringView { header.name }.equals_ignoring_ascii_case("Set-Cookie"sv)) - values.append(MUST(String::from_utf8(header.value))); + values.append(Infra::isomorphic_decode(header.value)); } return values; } @@ -152,10 +150,7 @@ WebIDL::ExceptionOr Headers::set(String const& name_string, String const& // 1. Normalize value. auto normalized_value = Infrastructure::normalize_header_value(value); - auto header = Infrastructure::Header { - .name = MUST(ByteBuffer::copy(name)), - .value = move(normalized_value), - }; + auto header = Infrastructure::Header::from_string_pair(name, normalized_value); // 2. If validating (name, value) for headers returns false, then return. if (!TRY(validate(header))) @@ -197,7 +192,7 @@ JS::ThrowCompletionOr Headers::for_each(ForEachCallback callback) auto const& pair = pairs[i]; // 2. Invoke idlCallback with « pair’s value, pair’s key, idlObject » and with thisArg as the callback this value. - TRY(callback(MUST(String::from_utf8(pair.name)), MUST(String::from_utf8(pair.value)))); + TRY(callback(Infra::isomorphic_decode(pair.name), Infra::isomorphic_decode(pair.value))); // 3. Set pairs to idlObject’s current list of value pairs to iterate over. (It might have changed.) pairs = value_pairs_to_iterate_over(); diff --git a/Libraries/LibWeb/Fetch/HeadersIterator.cpp b/Libraries/LibWeb/Fetch/HeadersIterator.cpp index 3b25fa055b94..8279fa27cdd0 100644 --- a/Libraries/LibWeb/Fetch/HeadersIterator.cpp +++ b/Libraries/LibWeb/Fetch/HeadersIterator.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::Bindings { @@ -65,8 +66,8 @@ GC::Ref HeadersIterator::next() return create_iterator_result_object(vm(), JS::js_undefined(), true); auto const& pair = pairs[m_index++]; - StringView pair_name { pair.name }; - StringView pair_value { pair.value }; + auto pair_name = Infra::isomorphic_decode(pair.name); + auto pair_value = Infra::isomorphic_decode(pair.value); switch (m_iteration_kind) { case JS::Object::PropertyKind::Key: diff --git a/Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt b/Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt new file mode 100644 index 000000000000..36d42bde131d --- /dev/null +++ b/Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt @@ -0,0 +1,34 @@ +-------------------------------- +Headers constructor +-------------------------------- +Accept: before-æøå-after +X-Test: before-ß-after + +-------------------------------- +Headers.append() +-------------------------------- +Accept: before-æøå-after +X-Test: before-ß-after + +-------------------------------- +Headers.set() +-------------------------------- +Accept: before-æøå-after +X-Test: before-ß-after + +-------------------------------- +Headers.getSetCookie() +-------------------------------- +Set-Cookie: before-æøå-after + +-------------------------------- +Headers iterator +-------------------------------- +accept: before-æøå-after +x-test: before-ß-after + +-------------------------------- +Headers.forEach() +-------------------------------- +accept: before-æøå-after +x-test: before-ß-after diff --git a/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt new file mode 100644 index 000000000000..6da8698508e7 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/fetch/api/headers/headers-normalize.any.txt @@ -0,0 +1,8 @@ +Harness status: OK + +Found 3 tests + +3 Pass +Pass Create headers with not normalized values +Pass Check append method with not normalized values +Pass Check set method with not normalized values \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html b/Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html new file mode 100644 index 000000000000..d2f066fcd820 --- /dev/null +++ b/Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html @@ -0,0 +1,66 @@ + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html new file mode 100644 index 000000000000..ac89e6d83447 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html @@ -0,0 +1,15 @@ + + +Headers normalize values + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js new file mode 100644 index 000000000000..68cf5b85f3ac --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.js @@ -0,0 +1,56 @@ +// META: title=Headers normalize values +// META: global=window,worker + +"use strict"; + +const expectations = { + "name1": [" space ", "space"], + "name2": ["\ttab\t", "tab"], + "name3": [" spaceAndTab\t", "spaceAndTab"], + "name4": ["\r\n newLine", "newLine"], //obs-fold cases + "name5": ["newLine\r\n ", "newLine"], + "name6": ["\r\n\tnewLine", "newLine"], + "name7": ["\t\f\tnewLine\n", "\f\tnewLine"], + "name8": ["newLine\xa0", "newLine\xa0"], // \xa0 == non breaking space +}; + +test(function () { + const headerDict = Object.fromEntries( + Object.entries(expectations).map(([name, [actual]]) => [name, actual]), + ); + var headers = new Headers(headerDict); + for (const name in expectations) { + const expected = expectations[name][1]; + assert_equals( + headers.get(name), + expected, + "name: " + name + " has normalized value: " + expected, + ); + } +}, "Create headers with not normalized values"); + +test(function () { + var headers = new Headers(); + for (const name in expectations) { + headers.append(name, expectations[name][0]); + const expected = expectations[name][1]; + assert_equals( + headers.get(name), + expected, + "name: " + name + " has value: " + expected, + ); + } +}, "Check append method with not normalized values"); + +test(function () { + var headers = new Headers(); + for (const name in expectations) { + headers.set(name, expectations[name][0]); + const expected = expectations[name][1]; + assert_equals( + headers.get(name), + expected, + "name: " + name + " has value: " + expected, + ); + } +}, "Check set method with not normalized values");