2022-07-11 21:42:14 +01:00
|
|
|
|
/*
|
2023-02-10 22:02:18 +00:00
|
|
|
|
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
|
2022-07-11 21:42:14 +01:00
|
|
|
|
* Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
|
2022-07-20 17:47:29 +01:00
|
|
|
|
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
2022-07-11 21:42:14 +01:00
|
|
|
|
*
|
|
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <AK/CharacterTypes.h>
|
|
|
|
|
|
#include <AK/Checked.h>
|
|
|
|
|
|
#include <AK/GenericLexer.h>
|
|
|
|
|
|
#include <AK/QuickSort.h>
|
2023-02-28 18:20:28 +00:00
|
|
|
|
#include <AK/StringUtils.h>
|
2022-10-30 01:52:07 +00:00
|
|
|
|
#include <LibJS/Runtime/VM.h>
|
2022-07-11 21:42:14 +01:00
|
|
|
|
#include <LibRegex/Regex.h>
|
2023-05-10 16:26:51 -04:00
|
|
|
|
#include <LibTextCodec/Decoder.h>
|
2025-11-24 12:20:51 -05:00
|
|
|
|
#include <LibTextCodec/Encoder.h>
|
2022-07-11 21:42:14 +01:00
|
|
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP.h>
|
|
|
|
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
|
2022-12-07 18:16:32 +00:00
|
|
|
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Methods.h>
|
2022-10-24 09:23:18 +01:00
|
|
|
|
#include <LibWeb/Loader/ResourceLoader.h>
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2022-07-17 23:52:02 +01:00
|
|
|
|
namespace Web::Fetch::Infrastructure {
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
|
GC_DEFINE_ALLOCATOR(HeaderList);
|
2024-04-06 10:16:04 -07:00
|
|
|
|
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Header Header::isomorphic_encode(StringView name, StringView value)
|
2024-10-22 11:47:22 +02:00
|
|
|
|
{
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return {
|
2025-11-24 12:20:51 -05:00
|
|
|
|
.name = TextCodec::isomorphic_encode(name),
|
|
|
|
|
|
.value = TextCodec::isomorphic_encode(value),
|
2022-10-24 09:16:32 +01:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// https://fetch.spec.whatwg.org/#extract-header-values
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Optional<Vector<ByteString>> Header::extract_header_values() const
|
2022-10-30 01:52:07 +00:00
|
|
|
|
{
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// FIXME: 1. If parsing header’s value, per the ABNF for header’s name, fails, then return failure.
|
|
|
|
|
|
// FIXME: 2. Return one or more values resulting from parsing header’s value, per the ABNF for header’s name.
|
2022-10-30 01:52:07 +00:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// For now we only parse some headers that are of the ABNF list form "#something"
|
2025-11-24 18:35:55 -05:00
|
|
|
|
if (name.is_one_of_ignoring_ascii_case(
|
2025-11-25 10:57:32 -05:00
|
|
|
|
"Access-Control-Request-Headers"sv,
|
|
|
|
|
|
"Access-Control-Expose-Headers"sv,
|
|
|
|
|
|
"Access-Control-Allow-Headers"sv,
|
|
|
|
|
|
"Access-Control-Allow-Methods"sv)
|
|
|
|
|
|
&& !value.is_empty()) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Vector<ByteString> trimmed_values;
|
2023-08-01 21:40:30 +12:00
|
|
|
|
|
2025-11-24 18:35:55 -05:00
|
|
|
|
value.view().for_each_split_view(',', SplitBehavior::Nothing, [&](auto value) {
|
|
|
|
|
|
trimmed_values.append(value.trim(" \t"sv));
|
|
|
|
|
|
});
|
2025-11-25 10:57:32 -05:00
|
|
|
|
|
|
|
|
|
|
return trimmed_values;
|
2023-08-01 21:40:30 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// This always ignores the ABNF rules for now and returns the header value as a single list item.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return Vector { value };
|
2025-11-25 10:57:32 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GC::Ref<HeaderList> HeaderList::create(JS::VM& vm)
|
|
|
|
|
|
{
|
|
|
|
|
|
return vm.heap().allocate<HeaderList>();
|
2023-08-01 21:40:30 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
// https://fetch.spec.whatwg.org/#header-list-contains
|
2025-11-24 18:35:55 -05:00
|
|
|
|
bool HeaderList::contains(StringView name) const
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// A header list list contains a header name name if list contains a header whose name is a byte-case-insensitive
|
|
|
|
|
|
// match for name.
|
2022-07-11 21:42:14 +01:00
|
|
|
|
return any_of(*this, [&](auto const& header) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return header.name.equals_ignoring_ascii_case(name);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-get
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Optional<ByteString> HeaderList::get(StringView name) const
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// To get a header name name from a header list list, run these steps:
|
|
|
|
|
|
|
|
|
|
|
|
// 1. If list does not contain name, then return null.
|
|
|
|
|
|
if (!contains(name))
|
2024-04-26 13:24:20 -04:00
|
|
|
|
return {};
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// 2. Return the values of all headers in list whose name is a byte-case-insensitive match for name, separated from
|
|
|
|
|
|
// each other by 0x2C 0x20, in order.
|
|
|
|
|
|
StringBuilder builder;
|
2025-11-26 20:05:36 -05:00
|
|
|
|
bool first = true;
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
for (auto const& header : *this) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
if (!header.name.equals_ignoring_ascii_case(name))
|
2022-07-11 21:42:14 +01:00
|
|
|
|
continue;
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
2025-11-26 20:05:36 -05:00
|
|
|
|
if (!first)
|
2025-11-24 18:35:55 -05:00
|
|
|
|
builder.append(", "sv);
|
2025-11-26 20:05:36 -05:00
|
|
|
|
|
2025-11-24 18:35:55 -05:00
|
|
|
|
builder.append(header.value);
|
2025-11-26 20:05:36 -05:00
|
|
|
|
first = false;
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
|
|
|
|
|
return builder.to_byte_string();
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Optional<Vector<String>> HeaderList::get_decode_and_split(StringView name) const
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// To get, decode, and split a header name name from header list list, run these steps:
|
|
|
|
|
|
|
2022-12-07 18:16:32 +00:00
|
|
|
|
// 1. Let value be the result of getting name from list.
|
2024-04-26 13:24:20 -04:00
|
|
|
|
auto value = get(name);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2022-12-07 18:16:32 +00:00
|
|
|
|
// 2. If value is null, then return null.
|
|
|
|
|
|
if (!value.has_value())
|
2024-04-26 13:24:20 -04:00
|
|
|
|
return {};
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2022-12-07 18:16:32 +00:00
|
|
|
|
// 3. Return the result of getting, decoding, and splitting value.
|
|
|
|
|
|
return get_decode_and_split_header_value(*value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-append
|
2024-04-26 13:24:20 -04:00
|
|
|
|
void HeaderList::append(Header header)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// To append a header (name, value) to a header list list, run these steps:
|
|
|
|
|
|
|
|
|
|
|
|
// 1. If list contains name, then set name to the first such header’s name.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// NOTE: This reuses the casing of the name of the header already in list, if any. If there are multiple matched
|
|
|
|
|
|
// headers their names will all be identical.
|
|
|
|
|
|
auto matching_header = first_matching([&](auto const& existing_header) {
|
|
|
|
|
|
return existing_header.name.equals_ignoring_ascii_case(header.name);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (matching_header.has_value())
|
|
|
|
|
|
header.name = matching_header->name;
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
|
|
|
|
|
// 2. Append (name, value) to list.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Vector::append(move(header));
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-delete
|
2025-11-24 18:35:55 -05:00
|
|
|
|
void HeaderList::delete_(StringView name)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// To delete a header name name from a header list list, remove all headers whose name is a byte-case-insensitive
|
|
|
|
|
|
// match for name from list.
|
2022-07-11 21:42:14 +01:00
|
|
|
|
remove_all_matching([&](auto const& header) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return header.name.equals_ignoring_ascii_case(name);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-set
|
2024-04-26 13:24:20 -04:00
|
|
|
|
void HeaderList::set(Header header)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// To set a header (name, value) in a header list list, run these steps:
|
|
|
|
|
|
|
|
|
|
|
|
// 1. If list contains name, then set the value of the first such header to value and remove the others.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
auto it = find_if([&](auto const& existing_header) {
|
|
|
|
|
|
return existing_header.name.equals_ignoring_ascii_case(header.name);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (it != end()) {
|
|
|
|
|
|
it->value = move(header.value);
|
|
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
size_t i = 0;
|
|
|
|
|
|
remove_all_matching([&](auto const& existing_header) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
if (i++ <= it.index())
|
2022-07-11 21:42:14 +01:00
|
|
|
|
return false;
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
|
|
|
|
|
return existing_header.name.equals_ignoring_ascii_case(it->name);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// 2. Otherwise, append header (name, value) to list.
|
|
|
|
|
|
else {
|
2024-04-26 13:24:20 -04:00
|
|
|
|
append(move(header));
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-combine
|
2024-04-26 13:24:20 -04:00
|
|
|
|
void HeaderList::combine(Header header)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// To combine a header (name, value) in a header list list, run these steps:
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
|
|
|
|
|
// 1. If list contains name, then set the value of the first such header to its value, followed by 0x2C 0x20,
|
|
|
|
|
|
// followed by value.
|
|
|
|
|
|
auto matching_header = first_matching([&](auto const& existing_header) {
|
|
|
|
|
|
return existing_header.name.equals_ignoring_ascii_case(header.name);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (matching_header.has_value()) {
|
|
|
|
|
|
matching_header->value = ByteString::formatted("{}, {}", matching_header->value, header.value);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
// 2. Otherwise, append (name, value) to list.
|
|
|
|
|
|
else {
|
2024-04-26 13:24:20 -04:00
|
|
|
|
append(move(header));
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-19 00:19:14 +01:00
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
|
2024-04-26 13:24:20 -04:00
|
|
|
|
Vector<Header> HeaderList::sort_and_combine() const
|
2022-07-19 00:19:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// To sort and combine a header list list, run these steps:
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Let headers be an empty list of headers with the key being the name and value the value.
|
|
|
|
|
|
Vector<Header> headers;
|
|
|
|
|
|
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// 2. Let names be the result of convert header names to a sorted-lowercase set with all the names of the headers
|
|
|
|
|
|
// in list.
|
|
|
|
|
|
Vector<ByteString> names_list;
|
2024-04-26 13:24:20 -04:00
|
|
|
|
names_list.ensure_capacity(size());
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
2022-07-19 00:19:14 +01:00
|
|
|
|
for (auto const& header : *this)
|
2023-02-10 22:02:18 +00:00
|
|
|
|
names_list.unchecked_append(header.name);
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
2024-04-26 13:24:20 -04:00
|
|
|
|
auto names = convert_header_names_to_a_sorted_lowercase_set(names_list);
|
2022-07-19 00:19:14 +01:00
|
|
|
|
|
2022-12-07 18:29:17 +00:00
|
|
|
|
// 3. For each name of names:
|
2022-07-19 00:19:14 +01:00
|
|
|
|
for (auto& name : names) {
|
2023-02-10 22:02:18 +00:00
|
|
|
|
// 1. If name is `set-cookie`, then:
|
2025-11-24 18:35:55 -05:00
|
|
|
|
if (name == "set-cookie"sv) {
|
|
|
|
|
|
// 1. Let values be a list of all values of headers in list whose name is a byte-case-insensitive match for
|
|
|
|
|
|
// name, in order.
|
2023-02-10 22:02:18 +00:00
|
|
|
|
// 2. For each value of values:
|
|
|
|
|
|
for (auto const& [header_name, value] : *this) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
if (header_name.equals_ignoring_ascii_case(name)) {
|
2023-02-10 22:02:18 +00:00
|
|
|
|
// 1. Append (name, value) to headers.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
headers.append({ name, value });
|
2023-02-10 22:02:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 2. Otherwise:
|
|
|
|
|
|
else {
|
|
|
|
|
|
// 1. Let value be the result of getting name from list.
|
2024-04-26 13:24:20 -04:00
|
|
|
|
auto value = get(name);
|
2023-02-10 22:02:18 +00:00
|
|
|
|
|
|
|
|
|
|
// 2. Assert: value is not null.
|
|
|
|
|
|
VERIFY(value.has_value());
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Append (name, value) to headers.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
headers.empend(move(name), value.release_value());
|
2023-02-10 22:02:18 +00:00
|
|
|
|
}
|
2022-07-19 00:19:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Return headers.
|
|
|
|
|
|
return headers;
|
|
|
|
|
|
}
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// https://fetch.spec.whatwg.org/#extract-header-list-values
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Variant<Empty, Vector<ByteString>, HeaderList::ExtractHeaderParseFailure> HeaderList::extract_header_list_values(StringView name) const
|
2025-11-25 10:57:32 -05:00
|
|
|
|
{
|
|
|
|
|
|
// 1. If list does not contain name, then return null.
|
|
|
|
|
|
if (!contains(name))
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
// FIXME: 2. If the ABNF for name allows a single header and list contains more than one, then return failure.
|
|
|
|
|
|
// NOTE: If different error handling is needed, extract the desired header first.
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Let values be an empty list.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Vector<ByteString> values;
|
2025-11-25 10:57:32 -05:00
|
|
|
|
|
|
|
|
|
|
// 4. For each header header list contains whose name is name:
|
|
|
|
|
|
for (auto const& header : *this) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
if (!header.name.equals_ignoring_ascii_case(name))
|
2025-11-25 10:57:32 -05:00
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 1. Let extract be the result of extracting header values from header.
|
|
|
|
|
|
auto extract = header.extract_header_values();
|
|
|
|
|
|
|
|
|
|
|
|
// 2. If extract is failure, then return failure.
|
|
|
|
|
|
if (!extract.has_value())
|
|
|
|
|
|
return ExtractHeaderParseFailure {};
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Append each value in extract, in order, to values.
|
|
|
|
|
|
values.extend(extract.release_value());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Return values.
|
|
|
|
|
|
return values;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-02-28 18:20:28 +00:00
|
|
|
|
// https://fetch.spec.whatwg.org/#header-list-extract-a-length
|
2025-11-25 10:57:32 -05:00
|
|
|
|
Variant<Empty, u64, HeaderList::ExtractLengthFailure> HeaderList::extract_length() const
|
2023-02-28 18:20:28 +00:00
|
|
|
|
{
|
|
|
|
|
|
// 1. Let values be the result of getting, decoding, and splitting `Content-Length` from headers.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
auto values = get_decode_and_split("Content-Length"sv);
|
2023-02-28 18:20:28 +00:00
|
|
|
|
|
|
|
|
|
|
// 2. If values is null, then return null.
|
|
|
|
|
|
if (!values.has_value())
|
2025-11-25 10:57:32 -05:00
|
|
|
|
return {};
|
2023-02-28 18:20:28 +00:00
|
|
|
|
|
|
|
|
|
|
// 3. Let candidateValue be null.
|
|
|
|
|
|
Optional<String> candidate_value;
|
|
|
|
|
|
|
|
|
|
|
|
// 4. For each value of values:
|
|
|
|
|
|
for (auto const& value : *values) {
|
|
|
|
|
|
// 1. If candidateValue is null, then set candidateValue to value.
|
|
|
|
|
|
if (!candidate_value.has_value()) {
|
|
|
|
|
|
candidate_value = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 2. Otherwise, if value is not candidateValue, return failure.
|
|
|
|
|
|
else if (candidate_value.value() != value) {
|
|
|
|
|
|
return ExtractLengthFailure {};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. If candidateValue is the empty string or has a code point that is not an ASCII digit, then return null.
|
|
|
|
|
|
// 6. Return candidateValue, interpreted as decimal number.
|
|
|
|
|
|
// FIXME: This will return an empty Optional if it cannot fit into a u64, is this correct?
|
2025-11-25 10:57:32 -05:00
|
|
|
|
auto result = candidate_value->to_number<u64>(TrimWhitespace::No);
|
|
|
|
|
|
if (!result.has_value())
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
return *result;
|
2023-02-28 18:20:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// Non-standard
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Vector<ByteString> HeaderList::unique_names() const
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Vector<ByteString> header_names_set;
|
|
|
|
|
|
HashTable<StringView, CaseInsensitiveStringTraits> header_names_seen;
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
for (auto const& header : *this) {
|
|
|
|
|
|
if (header_names_seen.contains(header.name))
|
2022-07-11 21:42:14 +01:00
|
|
|
|
continue;
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
|
|
|
|
|
header_names_set.append(header.name);
|
2025-11-25 10:57:32 -05:00
|
|
|
|
header_names_seen.set(header.name);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
return header_names_set;
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#header-name
|
2025-11-24 18:35:55 -05:00
|
|
|
|
bool is_header_name(StringView header_name)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// A header name is a byte sequence that matches the field-name token production.
|
|
|
|
|
|
Regex<ECMA262Parser> regex { R"~~~(^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$)~~~" };
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return regex.has_match(header_name);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#header-value
|
2025-11-24 18:35:55 -05:00
|
|
|
|
bool is_header_value(StringView header_value)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// A header value is a byte sequence that matches the following conditions:
|
|
|
|
|
|
// - Has no leading or trailing HTTP tab or space bytes.
|
|
|
|
|
|
// - Contains no 0x00 (NUL) or HTTP newline bytes.
|
|
|
|
|
|
if (header_value.is_empty())
|
|
|
|
|
|
return true;
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
auto first_byte = header_value[0];
|
2025-11-24 18:35:55 -05:00
|
|
|
|
auto last_byte = header_value[header_value.length() - 1];
|
|
|
|
|
|
|
|
|
|
|
|
if (is_http_tab_or_space(first_byte) || is_http_tab_or_space(last_byte))
|
2022-07-11 21:42:14 +01:00
|
|
|
|
return false;
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
return !any_of(header_value, [](auto byte) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return byte == 0x00 || is_http_newline(byte);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#concept-header-value-normalize
|
2025-11-24 18:35:55 -05:00
|
|
|
|
ByteString normalize_header_value(StringView potential_value)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// To normalize a byte sequence potentialValue, remove any leading and trailing HTTP whitespace bytes from
|
|
|
|
|
|
// potentialValue.
|
2022-07-11 21:42:14 +01:00
|
|
|
|
if (potential_value.is_empty())
|
2024-04-26 13:24:20 -04:00
|
|
|
|
return {};
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return potential_value.trim(HTTP_WHITESPACE, TrimMode::Both);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// https://fetch.spec.whatwg.org/#forbidden-header-name
|
|
|
|
|
|
bool is_forbidden_request_header(Header const& header)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// A header (name, value) is forbidden request-header if these steps return true:
|
2025-11-24 18:35:55 -05:00
|
|
|
|
auto const& [name, value] = header;
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 1. If name is a byte-case-insensitive match for one of:
|
|
|
|
|
|
// [...]
|
|
|
|
|
|
// then return true.
|
|
|
|
|
|
if (name.is_one_of_ignoring_ascii_case(
|
|
|
|
|
|
"Accept-Charset"sv,
|
|
|
|
|
|
"Accept-Encoding"sv,
|
|
|
|
|
|
"Access-Control-Request-Headers"sv,
|
|
|
|
|
|
"Access-Control-Request-Method"sv,
|
|
|
|
|
|
"Connection"sv,
|
|
|
|
|
|
"Content-Length"sv,
|
|
|
|
|
|
"Cookie"sv,
|
|
|
|
|
|
"Cookie2"sv,
|
|
|
|
|
|
"Date"sv,
|
|
|
|
|
|
"DNT"sv,
|
|
|
|
|
|
"Expect"sv,
|
|
|
|
|
|
"Host"sv,
|
|
|
|
|
|
"Keep-Alive"sv,
|
|
|
|
|
|
"Origin"sv,
|
|
|
|
|
|
"Referer"sv,
|
|
|
|
|
|
"Set-Cookie"sv,
|
|
|
|
|
|
"TE"sv,
|
|
|
|
|
|
"Trailer"sv,
|
|
|
|
|
|
"Transfer-Encoding"sv,
|
|
|
|
|
|
"Upgrade"sv,
|
|
|
|
|
|
"Via"sv)) {
|
|
|
|
|
|
return true;
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 2. If name when byte-lowercased starts with `proxy-` or `sec-`, then return true.
|
|
|
|
|
|
if (name.starts_with("proxy-"sv, CaseSensitivity::CaseInsensitive)
|
|
|
|
|
|
|| name.starts_with("sec-"sv, CaseSensitivity::CaseInsensitive)) {
|
|
|
|
|
|
return true;
|
2022-12-07 18:16:32 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. If name is a byte-case-insensitive match for one of:
|
|
|
|
|
|
// - `X-HTTP-Method`
|
|
|
|
|
|
// - `X-HTTP-Method-Override`
|
|
|
|
|
|
// - `X-Method-Override`
|
|
|
|
|
|
// then:
|
2023-03-10 08:48:54 +01:00
|
|
|
|
if (name.is_one_of_ignoring_ascii_case(
|
2022-12-07 18:16:32 +00:00
|
|
|
|
"X-HTTP-Method"sv,
|
|
|
|
|
|
"X-HTTP-Method-Override"sv,
|
2024-08-04 11:53:52 +01:00
|
|
|
|
"X-Method-Override"sv)) {
|
2022-12-07 18:16:32 +00:00
|
|
|
|
// 1. Let parsedValues be the result of getting, decoding, and splitting value.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
auto parsed_values = get_decode_and_split_header_value(value);
|
2022-12-07 18:16:32 +00:00
|
|
|
|
|
2022-12-07 18:29:17 +00:00
|
|
|
|
// 2. For each method of parsedValues: if the isomorphic encoding of method is a forbidden method, then return true.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// NB: The values returned from get_decode_and_split_header_value have already been decoded.
|
|
|
|
|
|
if (any_of(parsed_values, [](auto const& method) { return is_forbidden_method(method); }))
|
2022-12-07 18:16:32 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Return false.
|
|
|
|
|
|
return false;
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// https://fetch.spec.whatwg.org/#forbidden-response-header-name
|
2025-11-24 18:35:55 -05:00
|
|
|
|
bool is_forbidden_response_header_name(StringView header_name)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// A forbidden response-header name is a header name that is a byte-case-insensitive match for one of:
|
|
|
|
|
|
// - `Set-Cookie`
|
|
|
|
|
|
// - `Set-Cookie2`
|
2025-11-24 18:35:55 -05:00
|
|
|
|
return header_name.is_one_of_ignoring_ascii_case(
|
2022-07-11 21:42:14 +01:00
|
|
|
|
"Set-Cookie"sv,
|
|
|
|
|
|
"Set-Cookie2"sv);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// https://fetch.spec.whatwg.org/#header-value-get-decode-and-split
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Vector<String> get_decode_and_split_header_value(StringView value)
|
2022-10-25 23:02:47 +01:00
|
|
|
|
{
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// To get, decode, and split a header value value, run these steps:
|
2023-08-01 22:00:28 +12:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 1. Let input be the result of isomorphic decoding value.
|
2025-11-24 12:20:51 -05:00
|
|
|
|
auto input = TextCodec::isomorphic_decode(value);
|
2023-08-01 22:00:28 +12:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 2. Let position be a position variable for input, initially pointing at the start of input.
|
|
|
|
|
|
GenericLexer lexer { input };
|
|
|
|
|
|
|
|
|
|
|
|
// 3. Let values be a list of strings, initially « ».
|
|
|
|
|
|
Vector<String> values;
|
|
|
|
|
|
|
|
|
|
|
|
// 4. Let temporaryValue be the empty string.
|
|
|
|
|
|
StringBuilder temporary_value_builder;
|
|
|
|
|
|
|
|
|
|
|
|
// 5. While true:
|
|
|
|
|
|
while (true) {
|
2025-11-24 18:35:55 -05:00
|
|
|
|
// 1. Append the result of collecting a sequence of code points that are not U+0022 (") or U+002C (,) from
|
|
|
|
|
|
// input, given position, to temporaryValue.
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// NOTE: The result might be the empty string.
|
|
|
|
|
|
temporary_value_builder.append(lexer.consume_until(is_any_of("\","sv)));
|
|
|
|
|
|
|
|
|
|
|
|
// 2. If position is not past the end of input and the code point at position within input is U+0022 ("):
|
|
|
|
|
|
if (!lexer.is_eof() && lexer.peek() == '"') {
|
|
|
|
|
|
// 1. Append the result of collecting an HTTP quoted string from input, given position, to temporaryValue.
|
|
|
|
|
|
temporary_value_builder.append(collect_an_http_quoted_string(lexer));
|
|
|
|
|
|
|
|
|
|
|
|
// 2. If position is not past the end of input, then continue.
|
|
|
|
|
|
if (!lexer.is_eof())
|
|
|
|
|
|
continue;
|
2023-08-01 22:00:28 +12:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 3. Remove all HTTP tab or space from the start and end of temporaryValue.
|
|
|
|
|
|
auto temporary_value = MUST(String::from_utf8(temporary_value_builder.string_view().trim(HTTP_TAB_OR_SPACE, TrimMode::Both)));
|
2023-08-01 22:00:28 +12:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 4. Append temporaryValue to values.
|
|
|
|
|
|
values.append(move(temporary_value));
|
2022-10-25 23:02:47 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 5. Set temporaryValue to the empty string.
|
|
|
|
|
|
temporary_value_builder.clear();
|
2022-10-25 23:02:47 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 6. If position is past the end of input, then return values.
|
|
|
|
|
|
if (lexer.is_eof())
|
|
|
|
|
|
return values;
|
2022-10-25 23:02:47 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 7. Assert: the code point at position within input is U+002C (,).
|
|
|
|
|
|
VERIFY(lexer.peek() == ',');
|
2022-10-25 23:02:47 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 8. Advance position by 1.
|
|
|
|
|
|
lexer.ignore(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-10-25 23:02:47 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// https://fetch.spec.whatwg.org/#convert-header-names-to-a-sorted-lowercase-set
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Vector<ByteString> convert_header_names_to_a_sorted_lowercase_set(ReadonlySpan<ByteString> header_names)
|
2025-11-25 10:57:32 -05:00
|
|
|
|
{
|
|
|
|
|
|
// To convert header names to a sorted-lowercase set, given a list of names headerNames, run these steps:
|
2022-10-25 23:02:47 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 1. Let headerNamesSet be a new ordered set.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
HashTable<StringView, CaseInsensitiveStringTraits> header_names_seen;
|
|
|
|
|
|
Vector<ByteString> header_names_set;
|
2022-10-25 23:02:47 +01:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 2. For each name of headerNames, append the result of byte-lowercasing name to headerNamesSet.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
for (auto const& name : header_names) {
|
2025-11-25 10:57:32 -05:00
|
|
|
|
if (header_names_seen.contains(name))
|
|
|
|
|
|
continue;
|
2025-11-24 18:35:55 -05:00
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
header_names_seen.set(name);
|
2025-11-24 18:35:55 -05:00
|
|
|
|
header_names_set.append(name.to_lowercase());
|
2022-10-25 23:02:47 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-25 10:57:32 -05:00
|
|
|
|
// 3. Return the result of sorting headerNamesSet in ascending order with byte less than.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
quick_sort(header_names_set);
|
|
|
|
|
|
return header_names_set;
|
2022-10-25 23:02:47 +01:00
|
|
|
|
}
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:20:33 -06:00
|
|
|
|
// https://fetch.spec.whatwg.org/#build-a-content-range
|
2025-11-25 11:02:12 -05:00
|
|
|
|
ByteString build_content_range(u64 range_start, u64 range_end, u64 full_length)
|
2024-11-18 17:20:33 -06:00
|
|
|
|
{
|
|
|
|
|
|
// 1. Let contentRange be `bytes `.
|
|
|
|
|
|
// 2. Append rangeStart, serialized and isomorphic encoded, to contentRange.
|
|
|
|
|
|
// 3. Append 0x2D (-) to contentRange.
|
|
|
|
|
|
// 4. Append rangeEnd, serialized and isomorphic encoded to contentRange.
|
|
|
|
|
|
// 5. Append 0x2F (/) to contentRange.
|
|
|
|
|
|
// 6. Append fullLength, serialized and isomorphic encoded to contentRange.
|
|
|
|
|
|
// 7. Return contentRange.
|
2025-11-25 11:02:12 -05:00
|
|
|
|
return ByteString::formatted("bytes {}-{}/{}", range_start, range_end, full_length);
|
2024-11-18 17:20:33 -06:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
// https://fetch.spec.whatwg.org/#simple-range-header-value
|
2025-11-24 18:35:55 -05:00
|
|
|
|
Optional<RangeHeaderValue> parse_single_range_header_value(StringView const value, bool const allow_whitespace)
|
2022-07-11 21:42:14 +01:00
|
|
|
|
{
|
|
|
|
|
|
// 1. Let data be the isomorphic decoding of value.
|
2025-11-24 12:20:51 -05:00
|
|
|
|
auto const data = TextCodec::isomorphic_decode(value);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 2. If data does not start with "bytes", then return failure.
|
|
|
|
|
|
if (!data.starts_with_bytes("bytes"sv))
|
2022-10-15 00:39:40 +02:00
|
|
|
|
return {};
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 3. Let position be a position variable for data, initially pointing at the 5th code point of data.
|
2025-11-25 10:57:32 -05:00
|
|
|
|
GenericLexer lexer { data };
|
2024-11-18 17:18:26 -06:00
|
|
|
|
lexer.ignore(5);
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 4. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
|
|
|
|
|
|
if (allow_whitespace)
|
|
|
|
|
|
lexer.consume_while(is_http_tab_or_space);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. If the code point at position within data is not U+003D (=), then return failure.
|
|
|
|
|
|
// 6. Advance position by 1.
|
|
|
|
|
|
if (!lexer.consume_specific('='))
|
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
|
|
// 7. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
|
|
|
|
|
|
if (allow_whitespace)
|
|
|
|
|
|
lexer.consume_while(is_http_tab_or_space);
|
|
|
|
|
|
|
|
|
|
|
|
// 8. Let rangeStart be the result of collecting a sequence of code points that are ASCII digits, from data given position.
|
2022-07-11 21:42:14 +01:00
|
|
|
|
auto range_start = lexer.consume_while(is_ascii_digit);
|
|
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 9. Let rangeStartValue be rangeStart, interpreted as decimal number, if rangeStart is not the empty string; otherwise null.
|
2023-12-23 15:59:14 +13:00
|
|
|
|
auto range_start_value = range_start.to_number<u64>();
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 10. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
|
|
|
|
|
|
if (allow_whitespace)
|
|
|
|
|
|
lexer.consume_while(is_http_tab_or_space);
|
|
|
|
|
|
|
|
|
|
|
|
// 11. If the code point at position within data is not U+002D (-), then return failure.
|
|
|
|
|
|
// 12. Advance position by 1.
|
2022-07-11 21:42:14 +01:00
|
|
|
|
if (!lexer.consume_specific('-'))
|
2022-10-15 00:39:40 +02:00
|
|
|
|
return {};
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 13. If allowWhitespace is true, collect a sequence of code points that are HTTP tab or space, from data given position.
|
|
|
|
|
|
if (allow_whitespace)
|
|
|
|
|
|
lexer.consume_while(is_http_tab_or_space);
|
|
|
|
|
|
|
|
|
|
|
|
// 14. Let rangeEnd be the result of collecting a sequence of code points that are ASCII digits, from data given position.
|
2022-07-11 21:42:14 +01:00
|
|
|
|
auto range_end = lexer.consume_while(is_ascii_digit);
|
|
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 15. Let rangeEndValue be rangeEnd, interpreted as decimal number, if rangeEnd is not the empty string; otherwise null.
|
2023-12-23 15:59:14 +13:00
|
|
|
|
auto range_end_value = range_end.to_number<u64>();
|
2022-10-15 00:39:40 +02:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 16. If position is not past the end of data, then return failure.
|
2022-07-11 21:42:14 +01:00
|
|
|
|
if (!lexer.is_eof())
|
2022-10-15 00:39:40 +02:00
|
|
|
|
return {};
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 17. If rangeEndValue and rangeStartValue are null, then return failure.
|
2022-10-15 00:39:40 +02:00
|
|
|
|
if (!range_end_value.has_value() && !range_start_value.has_value())
|
|
|
|
|
|
return {};
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 18. If rangeStartValue and rangeEndValue are numbers, and rangeStartValue is greater than rangeEndValue, then return failure.
|
2022-10-15 00:39:40 +02:00
|
|
|
|
if (range_start_value.has_value() && range_end_value.has_value() && *range_start_value > *range_end_value)
|
|
|
|
|
|
return {};
|
2022-07-11 21:42:14 +01:00
|
|
|
|
|
2024-11-18 17:18:26 -06:00
|
|
|
|
// 19. Return (rangeStartValue, rangeEndValue).
|
2022-10-15 00:39:40 +02:00
|
|
|
|
return RangeHeaderValue { move(range_start_value), move(range_end_value) };
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-10-24 09:23:18 +01:00
|
|
|
|
// https://fetch.spec.whatwg.org/#default-user-agent-value
|
2025-11-24 18:35:55 -05:00
|
|
|
|
ByteString const& default_user_agent_value()
|
2022-10-24 09:23:18 +01:00
|
|
|
|
{
|
|
|
|
|
|
// A default `User-Agent` value is an implementation-defined header value for the `User-Agent` header.
|
2025-11-24 18:35:55 -05:00
|
|
|
|
static auto user_agent = ResourceLoader::the().user_agent().to_byte_string();
|
|
|
|
|
|
return user_agent;
|
2022-10-24 09:23:18 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-11 21:42:14 +01:00
|
|
|
|
}
|