Ver Fonte

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
Feng Yu há 7 meses atrás
pai
commit
824e91ffdb

+ 6 - 11
Libraries/LibWeb/Fetch/Headers.cpp

@@ -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 {
 
@@ -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 {};
 }
@@ -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
@@ -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;
 }
@@ -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)))
@@ -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();

+ 3 - 2
Libraries/LibWeb/Fetch/HeadersIterator.cpp

@@ -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 {
 
@@ -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:

+ 34 - 0
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

+ 8 - 0
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

+ 66 - 0
Tests/LibWeb/Text/input/Fetch/fetch-headers-non-ascii.html

@@ -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>

+ 15 - 0
Tests/LibWeb/Text/input/wpt-import/fetch/api/headers/headers-normalize.any.html

@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Headers normalize values</title>
+<script>
+self.GLOBAL = {
+  isWindow: function() { return true; },
+  isWorker: function() { return false; },
+  isShadowRealm: function() { return false; },
+};
+</script>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+
+<div id=log></div>
+<script src="../../../fetch/api/headers/headers-normalize.any.js"></script>

+ 56 - 0
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");