/* * Copyright (c) 2022-2023, Linus Groh * Copyright (c) 2022, Kenneth Myhra * Copyright (c) 2022, Luke Wilde * * SPDX-License-Identifier: BSD-2-Clause */ #include #include namespace HTTP { NonnullRefPtr HeaderList::create(Vector
headers) { return adopt_ref(*new HeaderList { move(headers) }); } HeaderList::HeaderList(Vector
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 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> 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
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
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 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, 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 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 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 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(TrimWhitespace::No); if (!result.has_value()) return {}; return *result; } // Non-standard Vector HeaderList::unique_names() const { HashTable header_names_seen; Vector 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; } }