mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-08 06:09:58 +00:00
277 lines
9.1 KiB
C++
277 lines
9.1 KiB
C++
|
|
/*
|
|||
|
|
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
|
|||
|
|
* Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
|
|||
|
|
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
|||
|
|
*
|
|||
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
#include <AK/StringUtils.h>
|
|||
|
|
#include <LibHTTP/HeaderList.h>
|
|||
|
|
|
|||
|
|
namespace HTTP {
|
|||
|
|
|
|||
|
|
NonnullRefPtr<HeaderList> HeaderList::create(Vector<Header> headers)
|
|||
|
|
{
|
|||
|
|
return adopt_ref(*new HeaderList { move(headers) });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HeaderList::HeaderList(Vector<Header> headers)
|
|||
|
|
: m_headers(move(headers))
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#header-list-contains
|
|||
|
|
bool HeaderList::contains(StringView name) const
|
|||
|
|
{
|
|||
|
|
// A header list list contains a header name name if list contains a header whose name is a byte-case-insensitive
|
|||
|
|
// match for name.
|
|||
|
|
return any_of(m_headers, [&](auto const& header) {
|
|||
|
|
return header.name.equals_ignoring_ascii_case(name);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-get
|
|||
|
|
Optional<ByteString> HeaderList::get(StringView name) const
|
|||
|
|
{
|
|||
|
|
// 1. If list does not contain name, then return null.
|
|||
|
|
if (!contains(name))
|
|||
|
|
return {};
|
|||
|
|
|
|||
|
|
// 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;
|
|||
|
|
bool first = true;
|
|||
|
|
|
|||
|
|
for (auto const& header : *this) {
|
|||
|
|
if (!header.name.equals_ignoring_ascii_case(name))
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
if (!first)
|
|||
|
|
builder.append(", "sv);
|
|||
|
|
|
|||
|
|
builder.append(header.value);
|
|||
|
|
first = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return builder.to_byte_string();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
|
|||
|
|
Optional<Vector<String>> HeaderList::get_decode_and_split(StringView name) const
|
|||
|
|
{
|
|||
|
|
// 1. Let value be the result of getting name from list.
|
|||
|
|
auto value = get(name);
|
|||
|
|
|
|||
|
|
// 2. If value is null, then return null.
|
|||
|
|
if (!value.has_value())
|
|||
|
|
return {};
|
|||
|
|
|
|||
|
|
// 3. Return the result of getting, decoding, and splitting value.
|
|||
|
|
return get_decode_and_split_header_value(*value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-append
|
|||
|
|
void HeaderList::append(Header header)
|
|||
|
|
{
|
|||
|
|
// 1. If list contains name, then set name to the first such header’s name.
|
|||
|
|
// 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 = m_headers.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;
|
|||
|
|
|
|||
|
|
// 2. Append (name, value) to list.
|
|||
|
|
m_headers.append(move(header));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-delete
|
|||
|
|
void HeaderList::delete_(StringView name)
|
|||
|
|
{
|
|||
|
|
// 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.
|
|||
|
|
m_headers.remove_all_matching([&](auto const& header) {
|
|||
|
|
return header.name.equals_ignoring_ascii_case(name);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-set
|
|||
|
|
void HeaderList::set(Header header)
|
|||
|
|
{
|
|||
|
|
// 1. If list contains name, then set the value of the first such header to value and remove the others.
|
|||
|
|
auto it = m_headers.find_if([&](auto const& existing_header) {
|
|||
|
|
return existing_header.name.equals_ignoring_ascii_case(header.name);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (it != m_headers.end()) {
|
|||
|
|
it->value = move(header.value);
|
|||
|
|
|
|||
|
|
size_t i = 0;
|
|||
|
|
m_headers.remove_all_matching([&](auto const& existing_header) {
|
|||
|
|
if (i++ <= it.index())
|
|||
|
|
return false;
|
|||
|
|
|
|||
|
|
return existing_header.name.equals_ignoring_ascii_case(it->name);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// 2. Otherwise, append header (name, value) to list.
|
|||
|
|
else {
|
|||
|
|
append(move(header));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-combine
|
|||
|
|
void HeaderList::combine(Header header)
|
|||
|
|
{
|
|||
|
|
// 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 = m_headers.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);
|
|||
|
|
}
|
|||
|
|
// 2. Otherwise, append (name, value) to list.
|
|||
|
|
else {
|
|||
|
|
append(move(header));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
|
|||
|
|
Vector<Header> HeaderList::sort_and_combine() const
|
|||
|
|
{
|
|||
|
|
// 1. Let headers be an empty list of headers with the key being the name and value the value.
|
|||
|
|
Vector<Header> headers;
|
|||
|
|
|
|||
|
|
// 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;
|
|||
|
|
names_list.ensure_capacity(m_headers.size());
|
|||
|
|
|
|||
|
|
for (auto const& header : *this)
|
|||
|
|
names_list.unchecked_append(header.name);
|
|||
|
|
|
|||
|
|
auto names = convert_header_names_to_a_sorted_lowercase_set(names_list);
|
|||
|
|
|
|||
|
|
// 3. For each name of names:
|
|||
|
|
for (auto& name : names) {
|
|||
|
|
// 1. If name is `set-cookie`, then:
|
|||
|
|
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.
|
|||
|
|
// 2. For each value of values:
|
|||
|
|
for (auto const& [header_name, value] : *this) {
|
|||
|
|
if (header_name.equals_ignoring_ascii_case(name)) {
|
|||
|
|
// 1. Append (name, value) to headers.
|
|||
|
|
headers.empend(name, value);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 2. Otherwise:
|
|||
|
|
else {
|
|||
|
|
// 1. Let value be the result of getting name from list.
|
|||
|
|
auto value = get(name);
|
|||
|
|
|
|||
|
|
// 2. Assert: value is not null.
|
|||
|
|
VERIFY(value.has_value());
|
|||
|
|
|
|||
|
|
// 3. Append (name, value) to headers.
|
|||
|
|
headers.empend(move(name), value.release_value());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. Return headers.
|
|||
|
|
return headers;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#extract-header-list-values
|
|||
|
|
Variant<Empty, Vector<ByteString>, HeaderList::ExtractHeaderParseFailure> HeaderList::extract_header_list_values(StringView name) const
|
|||
|
|
{
|
|||
|
|
// 1. If list does not contain name, then return null.
|
|||
|
|
if (!contains(name))
|
|||
|
|
return Empty {};
|
|||
|
|
|
|||
|
|
// 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.
|
|||
|
|
Vector<ByteString> values;
|
|||
|
|
|
|||
|
|
// 4. For each header header list contains whose name is name:
|
|||
|
|
for (auto const& header : m_headers) {
|
|||
|
|
if (!header.name.equals_ignoring_ascii_case(name))
|
|||
|
|
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;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// https://fetch.spec.whatwg.org/#header-list-extract-a-length
|
|||
|
|
Variant<Empty, u64, HeaderList::ExtractLengthFailure> HeaderList::extract_length() const
|
|||
|
|
{
|
|||
|
|
// 1. Let values be the result of getting, decoding, and splitting `Content-Length` from headers.
|
|||
|
|
auto values = get_decode_and_split("Content-Length"sv);
|
|||
|
|
|
|||
|
|
// 2. If values is null, then return null.
|
|||
|
|
if (!values.has_value())
|
|||
|
|
return {};
|
|||
|
|
|
|||
|
|
// 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?
|
|||
|
|
auto result = candidate_value->to_number<u64>(TrimWhitespace::No);
|
|||
|
|
if (!result.has_value())
|
|||
|
|
return {};
|
|||
|
|
|
|||
|
|
return *result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Non-standard
|
|||
|
|
Vector<ByteString> HeaderList::unique_names() const
|
|||
|
|
{
|
|||
|
|
HashTable<StringView, CaseInsensitiveStringTraits> header_names_seen;
|
|||
|
|
Vector<ByteString> header_names;
|
|||
|
|
|
|||
|
|
for (auto const& header : m_headers) {
|
|||
|
|
if (header_names_seen.contains(header.name))
|
|||
|
|
continue;
|
|||
|
|
|
|||
|
|
header_names_seen.set(header.name);
|
|||
|
|
header_names.append(header.name);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return header_names;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|