Skip to content

Commit

Permalink
LibWeb: Ensure Headers API can handle non-ascii characters
Browse files Browse the repository at this point in the history
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 #1893
  • Loading branch information
F3n67u committed Dec 7, 2024
1 parent 46b9518 commit dc24a87
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 13 deletions.
17 changes: 6 additions & 11 deletions Libraries/LibWeb/Fetch/Headers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <LibWeb/Bindings/HeadersPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Fetch/Headers.h>
#include <LibWeb/Infra/Strings.h>

namespace Web::Fetch {

Expand Down Expand Up @@ -56,10 +57,7 @@ void Headers::visit_edges(JS::Cell::Visitor& visitor)
WebIDL::ExceptionOr<void> 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 {};
}
Expand Down Expand Up @@ -106,7 +104,7 @@ WebIDL::ExceptionOr<Optional<String>> 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<String> {};
return byte_buffer.has_value() ? Infra::isomorphic_decode(*byte_buffer) : Optional<String> {};
}

// https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
Expand All @@ -123,7 +121,7 @@ Vector<String> 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;
}
Expand Down Expand Up @@ -152,10 +150,7 @@ WebIDL::ExceptionOr<void> 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)))
Expand Down Expand Up @@ -197,7 +192,7 @@ JS::ThrowCompletionOr<void> 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();
Expand Down
5 changes: 3 additions & 2 deletions Libraries/LibWeb/Fetch/HeadersIterator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <LibWeb/Bindings/HeadersIteratorPrototype.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Fetch/HeadersIterator.h>
#include <LibWeb/Infra/Strings.h>

namespace Web::Bindings {

Expand Down Expand Up @@ -65,8 +66,8 @@ GC::Ref<JS::Object> 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:
Expand Down
34 changes: 34 additions & 0 deletions Tests/LibWeb/Text/expected/Fetch/fetch-headers-non-ascii.txt
Original file line number Diff line number Diff line change
@@ -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
66 changes: 66 additions & 0 deletions Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
// This test ensures that the Headers get() methods can handle non-ASCII latin1 characters: code points U+0080-U+00FF.
test(() => {
println("--------------------------------");
println("Headers constructor")
println("--------------------------------");
const headers = new Headers({
"Accept": "before-æøå-after",
"X-Test": "before-ß-after"
});
println("Accept: " + headers.get("Accept"));
println("X-Test: " + headers.get("X-Test"));

println("\n--------------------------------");
println("Headers.append()")
println("--------------------------------");
const headers2 = new Headers();
headers2.append("Accept", "before-æøå-after");
headers2.append("X-Test", "before-ß-after");
println("Accept: " + headers2.get("Accept"));
println("X-Test: " + headers2.get("X-Test"));

println("\n--------------------------------");
println("Headers.set()")
println("--------------------------------");
const headers3 = new Headers({
"X-Test": "should be overwritten"
});
headers3.set("Accept", "before-æøå-after");
headers3.set("X-Test", "before-ß-after");
println("Accept: " + headers3.get("Accept"));
println("X-Test: " + headers3.get("X-Test"));

println("\n--------------------------------");
println("Headers.getSetCookie()")
println("--------------------------------");
const headers4 = new Headers({
"Set-Cookie": "before-æøå-after",
});
println("Set-Cookie: " + headers4.getSetCookie());

println("\n--------------------------------");
println("Headers iterator")
println("--------------------------------");
const headers5 = new Headers({
"Accept": "before-æøå-after",
"X-Test": "before-ß-after"
});
for (const [key, value] of headers5) {
println(`${key}: ${value}`);
}

println("\n--------------------------------");
println("Headers.forEach()")
println("--------------------------------");
const headers6 = new Headers({
"Accept": "before-æøå-after",
"X-Test": "before-ß-after"
});
headers6.forEach((value, key) => {
println(`${key}: ${value}`);
});
});
</script>

0 comments on commit dc24a87

Please sign in to comment.