mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Store HTTP methods and headers as ByteString
The spec declares these as a byte sequence, which we then implemented as a ByteBuffer. This has become pretty awkward to deal with, as evidenced by the plethora of `MUST(ByteBuffer::copy(...))` and `.bytes()` calls everywhere inside Fetch. We would then treat the bytes as a string anyways by wrapping them in StringView everywhere. We now store these as a ByteString. This is more comfortable to deal with, and we no longer need to continually copy underlying storage (as ByteString is ref-counted). This work is largely preparatory for an upcoming HTTP header refactor.
This commit is contained in:
parent
ed27eea091
commit
f675cfe90f
Notes:
github-actions[bot]
2025-11-26 14:16:12 +00:00
Author: https://github.com/trflynn89
Commit: f675cfe90f
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6933
28 changed files with 480 additions and 651 deletions
|
|
@ -21,7 +21,7 @@ namespace Web::ContentSecurityPolicy {
|
|||
GC_DEFINE_ALLOCATOR(Policy);
|
||||
|
||||
// https://w3c.github.io/webappsec-csp/#abstract-opdef-parse-a-serialized-csp
|
||||
GC::Ref<Policy> Policy::parse_a_serialized_csp(GC::Heap& heap, Variant<ByteBuffer, String> serialized, Source source, Disposition disposition)
|
||||
GC::Ref<Policy> Policy::parse_a_serialized_csp(GC::Heap& heap, Variant<ByteString, String> serialized, Source source, Disposition disposition)
|
||||
{
|
||||
// To parse a serialized CSP, given a byte sequence or string serialized, a source source, and a disposition disposition,
|
||||
// execute the following steps.
|
||||
|
|
@ -31,7 +31,7 @@ GC::Ref<Policy> Policy::parse_a_serialized_csp(GC::Heap& heap, Variant<ByteBuffe
|
|||
// 1. If serialized is a byte sequence, then set serialized to be the result of isomorphic decoding serialized.
|
||||
auto serialized_string = serialized.has<String>()
|
||||
? serialized.get<String>()
|
||||
: Infra::isomorphic_decode(serialized.get<ByteBuffer>());
|
||||
: Infra::isomorphic_decode(serialized.get<ByteString>());
|
||||
|
||||
// 2. Let policy be a new policy with an empty directive set, a source of source, and a disposition of disposition.
|
||||
auto policy = heap.allocate<Policy>();
|
||||
|
|
@ -101,9 +101,9 @@ GC::Ref<PolicyList> Policy::parse_a_responses_content_security_policies(GC::Heap
|
|||
|
||||
// 2. For each token returned by extracting header list values given Content-Security-Policy and response’s header
|
||||
// list:
|
||||
auto enforce_policy_tokens_or_failure = response->header_list()->extract_header_list_values("Content-Security-Policy"sv.bytes());
|
||||
auto enforce_policy_tokens_or_failure = response->header_list()->extract_header_list_values("Content-Security-Policy"sv);
|
||||
|
||||
if (auto const* enforce_policy_tokens = enforce_policy_tokens_or_failure.get_pointer<Vector<ByteBuffer>>()) {
|
||||
if (auto const* enforce_policy_tokens = enforce_policy_tokens_or_failure.get_pointer<Vector<ByteString>>()) {
|
||||
for (auto const& enforce_policy_token : *enforce_policy_tokens) {
|
||||
// 1. Let policy be the result of parsing token, with a source of "header", and a disposition of "enforce".
|
||||
auto policy = parse_a_serialized_csp(heap, enforce_policy_token, Policy::Source::Header, Policy::Disposition::Enforce);
|
||||
|
|
@ -116,9 +116,9 @@ GC::Ref<PolicyList> Policy::parse_a_responses_content_security_policies(GC::Heap
|
|||
|
||||
// 3. For each token returned by extracting header list values given Content-Security-Policy-Report-Only and
|
||||
// response’s header list:
|
||||
auto report_policy_tokens_or_failure = response->header_list()->extract_header_list_values("Content-Security-Policy-Report-Only"sv.bytes());
|
||||
auto report_policy_tokens_or_failure = response->header_list()->extract_header_list_values("Content-Security-Policy-Report-Only"sv);
|
||||
|
||||
if (auto const* report_policy_tokens = enforce_policy_tokens_or_failure.get_pointer<Vector<ByteBuffer>>()) {
|
||||
if (auto const* report_policy_tokens = enforce_policy_tokens_or_failure.get_pointer<Vector<ByteString>>()) {
|
||||
for (auto const& report_policy_token : *report_policy_tokens) {
|
||||
// 1. Let policy be the result of parsing token, with a source of "header", and a disposition of "report".
|
||||
auto policy = parse_a_serialized_csp(heap, report_policy_token, Policy::Source::Header, Policy::Disposition::Report);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public:
|
|||
|
||||
~Policy() = default;
|
||||
|
||||
[[nodiscard]] static GC::Ref<Policy> parse_a_serialized_csp(GC::Heap&, Variant<ByteBuffer, String> serialized, Source source, Disposition disposition);
|
||||
[[nodiscard]] static GC::Ref<Policy> parse_a_serialized_csp(GC::Heap&, Variant<ByteString, String> serialized, Source source, Disposition disposition);
|
||||
[[nodiscard]] static GC::Ref<PolicyList> parse_a_responses_content_security_policies(GC::Heap&, GC::Ref<Fetch::Infrastructure::Response const> response);
|
||||
[[nodiscard]] static GC::Ref<Policy> create_from_serialized_policy(GC::Heap&, SerializedPolicy const&);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ GC_DEFINE_ALLOCATOR(Violation);
|
|||
Violation::Violation(GC::Ptr<JS::Object> global_object, GC::Ref<Policy const> policy, String directive)
|
||||
: m_global_object(global_object)
|
||||
, m_policy(policy)
|
||||
, m_effective_directive(directive)
|
||||
, m_effective_directive(move(directive))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ GC::Ref<Violation> Violation::create_a_violation_object_for_global_policy_and_di
|
|||
{
|
||||
// 1. Let violation be a new violation whose global object is global, policy is policy, effective directive is
|
||||
// directive, and resource is null.
|
||||
auto violation = realm.create<Violation>(global_object, policy, directive);
|
||||
auto violation = realm.create<Violation>(global_object, policy, move(directive));
|
||||
|
||||
// FIXME: 2. If the user agent is currently executing script, and can extract a source file’s URL, line number,
|
||||
// and column number from the global, set violation’s source file, line number, and column number
|
||||
|
|
@ -406,7 +406,7 @@ void Violation::report_a_violation(JS::Realm& realm)
|
|||
|
||||
// method
|
||||
// "POST"
|
||||
request->set_method(MUST(ByteBuffer::copy("POST"sv.bytes())));
|
||||
request->set_method("POST"sv);
|
||||
|
||||
// url
|
||||
// violation’s url
|
||||
|
|
@ -448,8 +448,7 @@ void Violation::report_a_violation(JS::Realm& realm)
|
|||
// A header list containing a single header whose name is "Content-Type", and value is
|
||||
// "application/csp-report"
|
||||
auto header_list = Fetch::Infrastructure::HeaderList::create(vm);
|
||||
auto content_type_header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "application/csp-report"sv);
|
||||
header_list->append(move(content_type_header));
|
||||
header_list->append({ "Content-Type"sv, "application/csp-report"sv });
|
||||
request->set_header_list(header_list);
|
||||
|
||||
// body
|
||||
|
|
|
|||
|
|
@ -385,15 +385,15 @@ WebIDL::ExceptionOr<GC::Ref<Document>> Document::create_and_initialize(Type type
|
|||
document->m_window = window;
|
||||
|
||||
// NOTE: Non-standard: Pull out the Last-Modified header for use in the lastModified property.
|
||||
if (auto maybe_last_modified = navigation_params.response->header_list()->get("Last-Modified"sv.bytes()); maybe_last_modified.has_value()) {
|
||||
if (auto maybe_last_modified = navigation_params.response->header_list()->get("Last-Modified"sv); maybe_last_modified.has_value()) {
|
||||
// rfc9110, 8.8.2: The Last-Modified header field must be in GMT time zone.
|
||||
// document->m_last_modified is in local time zone.
|
||||
document->m_last_modified = AK::UnixDateTime::parse("%a, %d %b %Y %H:%M:%S GMT"sv, maybe_last_modified.value(), true);
|
||||
}
|
||||
|
||||
// NOTE: Non-standard: Pull out the Content-Language header to determine the document's language.
|
||||
if (auto maybe_http_content_language = navigation_params.response->header_list()->get("Content-Language"sv.bytes()); maybe_http_content_language.has_value()) {
|
||||
if (auto maybe_content_language = String::from_utf8(maybe_http_content_language.value()); !maybe_content_language.is_error())
|
||||
if (auto maybe_http_content_language = navigation_params.response->header_list()->get("Content-Language"sv); maybe_http_content_language.has_value()) {
|
||||
if (auto maybe_content_language = String::from_byte_string(maybe_http_content_language.value()); !maybe_content_language.is_error())
|
||||
document->m_http_content_language = maybe_content_language.release_value();
|
||||
}
|
||||
|
||||
|
|
@ -423,7 +423,7 @@ WebIDL::ExceptionOr<GC::Ref<Document>> Document::create_and_initialize(Type type
|
|||
// navigationParams's response's service worker timing info.
|
||||
|
||||
// 15. If navigationParams's response has a `Refresh` header, then:
|
||||
if (auto maybe_refresh = navigation_params.response->header_list()->get("Refresh"sv.bytes()); maybe_refresh.has_value()) {
|
||||
if (auto maybe_refresh = navigation_params.response->header_list()->get("Refresh"sv); maybe_refresh.has_value()) {
|
||||
// 1. Let value be the isomorphic decoding of the value of the header.
|
||||
auto value = Infra::isomorphic_decode(maybe_refresh.value());
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
|
|||
Optional<u64> length {};
|
||||
|
||||
// 9. Let type be null.
|
||||
Optional<ByteBuffer> type {};
|
||||
Optional<ByteString> type {};
|
||||
|
||||
// 10. Switch on object.
|
||||
TRY(object.visit(
|
||||
|
|
@ -83,7 +83,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
|
|||
length = blob->size();
|
||||
// If object’s type attribute is not the empty byte sequence, set type to its value.
|
||||
if (!blob->type().is_empty())
|
||||
type = MUST(ByteBuffer::copy(blob->type().bytes()));
|
||||
type = blob->type().to_byte_string();
|
||||
return {};
|
||||
},
|
||||
[&](ReadonlyBytes bytes) -> WebIDL::ExceptionOr<void> {
|
||||
|
|
@ -103,8 +103,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
|
|||
source = serialized_form_data.serialized_data;
|
||||
// FIXME: Set length to unclear, see html/6424 for improving this.
|
||||
// Set type to `multipart/form-data; boundary=`, followed by the multipart/form-data boundary string generated by the multipart/form-data encoding algorithm.
|
||||
auto type_string = MUST(String::formatted("multipart/form-data; boundary={}", serialized_form_data.boundary));
|
||||
type = MUST(ByteBuffer::copy(type_string.bytes()));
|
||||
type = ByteString::formatted("multipart/form-data; boundary={}", serialized_form_data.boundary);
|
||||
return {};
|
||||
},
|
||||
[&](GC::Root<DOMURL::URLSearchParams> const& url_search_params) -> WebIDL::ExceptionOr<void> {
|
||||
|
|
@ -112,7 +111,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
|
|||
auto search_params_string = url_search_params->to_string();
|
||||
source = MUST(ByteBuffer::copy(search_params_string.bytes()));
|
||||
// Set type to `application/x-www-form-urlencoded;charset=UTF-8`.
|
||||
type = MUST(ByteBuffer::copy("application/x-www-form-urlencoded;charset=UTF-8"sv.bytes()));
|
||||
type = "application/x-www-form-urlencoded;charset=UTF-8"sv;
|
||||
return {};
|
||||
},
|
||||
[&](String const& scalar_value_string) -> WebIDL::ExceptionOr<void> {
|
||||
|
|
@ -120,7 +119,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
|
|||
// Set source to the UTF-8 encoding of object.
|
||||
source = MUST(ByteBuffer::copy(scalar_value_string.bytes()));
|
||||
// Set type to `text/plain;charset=UTF-8`.
|
||||
type = MUST(ByteBuffer::copy("text/plain;charset=UTF-8"sv.bytes()));
|
||||
type = "text/plain;charset=UTF-8"sv;
|
||||
return {};
|
||||
},
|
||||
[&](GC::Root<Streams::ReadableStream> const& stream) -> WebIDL::ExceptionOr<void> {
|
||||
|
|
@ -169,7 +168,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
|
|||
auto body = Infrastructure::Body::create(vm, *stream, move(source), move(length));
|
||||
|
||||
// 14. Return (body, type).
|
||||
return Infrastructure::BodyWithType { .body = move(body), .type = move(type) };
|
||||
return Infrastructure::BodyWithType { .body = body, .type = move(type) };
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Web::Fetch::Fetching {
|
|||
bool cors_check(Infrastructure::Request const& request, Infrastructure::Response const& response)
|
||||
{
|
||||
// 1. Let origin be the result of getting `Access-Control-Allow-Origin` from response’s header list.
|
||||
auto origin = response.header_list()->get("Access-Control-Allow-Origin"sv.bytes());
|
||||
auto origin = response.header_list()->get("Access-Control-Allow-Origin"sv);
|
||||
|
||||
// 2. If origin is null, then return failure.
|
||||
// NOTE: Null is not `null`.
|
||||
|
|
@ -23,7 +23,7 @@ bool cors_check(Infrastructure::Request const& request, Infrastructure::Response
|
|||
return false;
|
||||
|
||||
// 3. If request’s credentials mode is not "include" and origin is `*`, then return success.
|
||||
if (request.credentials_mode() != Infrastructure::Request::CredentialsMode::Include && origin->span() == "*"sv.bytes())
|
||||
if (request.credentials_mode() != Infrastructure::Request::CredentialsMode::Include && *origin == "*"sv)
|
||||
return true;
|
||||
|
||||
// 4. If the result of byte-serializing a request origin with request is not origin, then return failure.
|
||||
|
|
@ -35,10 +35,10 @@ bool cors_check(Infrastructure::Request const& request, Infrastructure::Response
|
|||
return true;
|
||||
|
||||
// 6. Let credentials be the result of getting `Access-Control-Allow-Credentials` from response’s header list.
|
||||
auto credentials = response.header_list()->get("Access-Control-Allow-Credentials"sv.bytes());
|
||||
auto credentials = response.header_list()->get("Access-Control-Allow-Credentials"sv);
|
||||
|
||||
// 7. If credentials is `true`, then return success.
|
||||
if (credentials.has_value() && credentials->span() == "true"sv.bytes())
|
||||
if (credentials == "true"sv)
|
||||
return true;
|
||||
|
||||
// 8. Return failure.
|
||||
|
|
@ -53,7 +53,7 @@ bool tao_check(Infrastructure::Request const& request, Infrastructure::Response
|
|||
return false;
|
||||
|
||||
// 2. Let values be the result of getting, decoding, and splitting `Timing-Allow-Origin` from response’s header list.
|
||||
auto values = response.header_list()->get_decode_and_split("Timing-Allow-Origin"sv.bytes());
|
||||
auto values = response.header_list()->get_decode_and_split("Timing-Allow-Origin"sv);
|
||||
|
||||
// 3. If values contains "*", then return success.
|
||||
if (values.has_value() && values->contains_slow("*"sv))
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/HTML/WorkerGlobalScope.h>
|
||||
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
#include <LibWeb/Loader/LoadRequest.h>
|
||||
#include <LibWeb/Loader/ResourceLoader.h>
|
||||
#include <LibWeb/MixedContent/AbstractOperations.h>
|
||||
|
|
@ -143,7 +144,7 @@ GC::Ref<Infrastructure::FetchController> fetch(JS::Realm& realm, Infrastructure:
|
|||
// - request’s client is not null, and request’s client’s global object is a Window object
|
||||
&& request.client() && is<HTML::Window>(request.client()->global_object())
|
||||
// - request’s method is `GET`
|
||||
&& StringView { request.method() }.equals_ignoring_ascii_case("GET"sv)
|
||||
&& request.method().equals_ignoring_ascii_case("GET"sv)
|
||||
// - request’s unsafe-request flag is not set or request’s header list is empty
|
||||
&& (!request.unsafe_request() || request.header_list()->is_empty())) {
|
||||
// 1. Assert: request’s origin is same origin with request’s client’s origin.
|
||||
|
|
@ -168,7 +169,7 @@ GC::Ref<Infrastructure::FetchController> fetch(JS::Realm& realm, Infrastructure:
|
|||
}
|
||||
|
||||
// 11. If request’s header list does not contain `Accept`, then:
|
||||
if (!request.header_list()->contains("Accept"sv.bytes())) {
|
||||
if (!request.header_list()->contains("Accept"sv)) {
|
||||
// 1. Let value be `*/*`.
|
||||
auto value = "*/*"sv;
|
||||
|
||||
|
|
@ -210,17 +211,17 @@ GC::Ref<Infrastructure::FetchController> fetch(JS::Realm& realm, Infrastructure:
|
|||
}
|
||||
|
||||
// 4. Append (`Accept`, value) to request’s header list.
|
||||
auto header = Infrastructure::Header::from_string_pair("Accept"sv, value.bytes());
|
||||
auto header = Infrastructure::Header::isomorphic_encode("Accept"sv, value);
|
||||
request.header_list()->append(move(header));
|
||||
}
|
||||
|
||||
// 12. If request’s header list does not contain `Accept-Language`, then user agents should append
|
||||
// (`Accept-Language, an appropriate header value) to request’s header list.
|
||||
if (!request.header_list()->contains("Accept-Language"sv.bytes())) {
|
||||
if (!request.header_list()->contains("Accept-Language"sv)) {
|
||||
StringBuilder accept_language;
|
||||
accept_language.join(","sv, ResourceLoader::the().preferred_languages());
|
||||
|
||||
auto header = Infrastructure::Header::from_string_pair("Accept-Language"sv, accept_language.string_view());
|
||||
auto header = Infrastructure::Header::isomorphic_encode("Accept-Language"sv, accept_language.string_view());
|
||||
request.header_list()->append(move(header));
|
||||
}
|
||||
|
||||
|
|
@ -495,13 +496,13 @@ GC::Ptr<PendingResponse> main_fetch(JS::Realm& realm, Infrastructure::FetchParam
|
|||
if (request->response_tainting() == Infrastructure::Request::ResponseTainting::CORS) {
|
||||
// 1. Let headerNames be the result of extracting header list values given
|
||||
// `Access-Control-Expose-Headers` and response’s header list.
|
||||
auto header_names_or_failure = response->header_list()->extract_header_list_values("Access-Control-Expose-Headers"sv.bytes());
|
||||
auto header_names_or_failure = response->header_list()->extract_header_list_values("Access-Control-Expose-Headers"sv);
|
||||
|
||||
if (auto* header_names = header_names_or_failure.get_pointer<Vector<ByteBuffer>>()) {
|
||||
if (auto* header_names = header_names_or_failure.get_pointer<Vector<ByteString>>()) {
|
||||
// 2. If request’s credentials mode is not "include" and headerNames contains `*`, then set
|
||||
// response’s CORS-exposed header-name list to all unique header names in response’s header
|
||||
// list.
|
||||
if (request->credentials_mode() != Infrastructure::Request::CredentialsMode::Include && header_names->contains_slow("*"sv.bytes())) {
|
||||
if (request->credentials_mode() != Infrastructure::Request::CredentialsMode::Include && header_names->contains_slow("*"sv)) {
|
||||
auto unique_header_names = response->header_list()->unique_names();
|
||||
response->set_cors_exposed_header_name_list(move(unique_header_names));
|
||||
}
|
||||
|
|
@ -576,7 +577,7 @@ GC::Ptr<PendingResponse> main_fetch(JS::Realm& realm, Infrastructure::FetchParam
|
|||
if (response->type() == Infrastructure::Response::Type::Opaque
|
||||
&& internal_response->status() == 206
|
||||
&& internal_response->range_requested()
|
||||
&& !request->header_list()->contains("Range"sv.bytes())) {
|
||||
&& !request->header_list()->contains("Range"sv)) {
|
||||
response = internal_response = Infrastructure::Response::network_error(vm, "Response has status 206 and 'range-requested' flag set, but request has no 'Range' header"_string);
|
||||
}
|
||||
|
||||
|
|
@ -584,7 +585,7 @@ GC::Ptr<PendingResponse> main_fetch(JS::Realm& realm, Infrastructure::FetchParam
|
|||
// internalResponse’s status is a null body status, set internalResponse’s body to null and disregard
|
||||
// any enqueuing toward it (if any).
|
||||
// NOTE: This standardizes the error handling for servers that violate HTTP.
|
||||
if (!response->is_network_error() && (StringView { request->method() }.is_one_of("HEAD"sv, "CONNECT"sv) || Infrastructure::is_null_body_status(internal_response->status())))
|
||||
if (!response->is_network_error() && (request->method().is_one_of("HEAD"sv, "CONNECT"sv) || Infrastructure::is_null_body_status(internal_response->status())))
|
||||
internal_response->set_body({});
|
||||
|
||||
// 22. If request’s integrity metadata is not the empty string, then:
|
||||
|
|
@ -660,7 +661,7 @@ void fetch_response_handover(JS::Realm& realm, Infrastructure::FetchParams const
|
|||
// The user agent may decide to expose `Server-Timing` headers to non-secure contexts requests as well.
|
||||
auto client = fetch_params.request()->client();
|
||||
if (!response.is_network_error() && client != nullptr && HTML::is_secure_context(*client)) {
|
||||
auto server_timing_headers = response.header_list()->get_decode_and_split("Server-Timing"sv.bytes());
|
||||
auto server_timing_headers = response.header_list()->get_decode_and_split("Server-Timing"sv);
|
||||
if (server_timing_headers.has_value())
|
||||
timing_info->set_server_timing_headers(server_timing_headers.release_value());
|
||||
}
|
||||
|
|
@ -846,12 +847,10 @@ GC::Ref<PendingResponse> scheme_fetch(JS::Realm& realm, Infrastructure::FetchPar
|
|||
// of fetching.
|
||||
if (request->current_url().paths().size() == 1 && request->current_url().paths()[0] == "blank"sv) {
|
||||
auto response = Infrastructure::Response::create(vm);
|
||||
response->set_status_message(MUST(ByteBuffer::copy("OK"sv.bytes())));
|
||||
|
||||
auto header = Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html;charset=utf-8"sv);
|
||||
response->header_list()->append(move(header));
|
||||
|
||||
response->set_status_message("OK"sv);
|
||||
response->header_list()->append({ "Content-Type"sv, "text/html;charset=utf-8"sv });
|
||||
response->set_body(Infrastructure::byte_sequence_as_body(realm, ""sv.bytes()));
|
||||
|
||||
return PendingResponse::create(vm, request, response);
|
||||
}
|
||||
|
||||
|
|
@ -864,7 +863,7 @@ GC::Ref<PendingResponse> scheme_fetch(JS::Realm& realm, Infrastructure::FetchPar
|
|||
auto const& blob_url_entry = request->current_url().blob_url_entry();
|
||||
|
||||
// 2. If request’s method is not `GET` or blobURLEntry is null, then return a network error. [FILEAPI]
|
||||
if (request->method() != "GET"sv.bytes() || !blob_url_entry.has_value())
|
||||
if (request->method() != "GET"sv || !blob_url_entry.has_value())
|
||||
return PendingResponse::create(vm, request, Infrastructure::Response::network_error(vm, "Request has an invalid 'blob:' URL"_string));
|
||||
|
||||
// 3. Let requestEnvironment be the result of determining the environment given request.
|
||||
|
|
@ -909,21 +908,21 @@ GC::Ref<PendingResponse> scheme_fetch(JS::Realm& realm, Infrastructure::FetchPar
|
|||
auto const& type = blob->type();
|
||||
|
||||
// 13. If request’s header list does not contain `Range`:
|
||||
if (!request->header_list()->contains("Range"sv.bytes())) {
|
||||
if (!request->header_list()->contains("Range"sv)) {
|
||||
// 1. Let bodyWithType be the result of safely extracting blob.
|
||||
auto body_with_type = safely_extract_body(realm, blob->raw_bytes());
|
||||
|
||||
// 2. Set response’s status message to `OK`.
|
||||
response->set_status_message(MUST(ByteBuffer::copy("OK"sv.bytes())));
|
||||
response->set_status_message("OK"sv);
|
||||
|
||||
// 3. Set response’s body to bodyWithType’s body.
|
||||
response->set_body(body_with_type.body);
|
||||
|
||||
// 4. Set response’s header list to « (`Content-Length`, serializedFullLength), (`Content-Type`, type) ».
|
||||
auto content_length_header = Infrastructure::Header::from_string_pair("Content-Length"sv, serialized_full_length);
|
||||
auto content_length_header = Infrastructure::Header::isomorphic_encode("Content-Length"sv, serialized_full_length);
|
||||
response->header_list()->append(move(content_length_header));
|
||||
|
||||
auto content_type_header = Infrastructure::Header::from_string_pair("Content-Type"sv, type);
|
||||
auto content_type_header = Infrastructure::Header::isomorphic_encode("Content-Type"sv, type);
|
||||
response->header_list()->append(move(content_type_header));
|
||||
}
|
||||
// 14. Otherwise:
|
||||
|
|
@ -932,7 +931,7 @@ GC::Ref<PendingResponse> scheme_fetch(JS::Realm& realm, Infrastructure::FetchPar
|
|||
response->set_range_requested(true);
|
||||
|
||||
// 2. Let rangeHeader be the result of getting `Range` from request’s header list.
|
||||
auto const range_header = request->header_list()->get("Range"sv.bytes()).value_or(ByteBuffer {});
|
||||
auto const range_header = request->header_list()->get("Range"sv).value_or({});
|
||||
|
||||
// 3. Let rangeValue be the result of parsing a single range header value given rangeHeader and true.
|
||||
auto maybe_range_value = Infrastructure::parse_single_range_header_value(range_header, true);
|
||||
|
|
@ -984,20 +983,20 @@ GC::Ref<PendingResponse> scheme_fetch(JS::Realm& realm, Infrastructure::FetchPar
|
|||
response->set_status(206);
|
||||
|
||||
// 14. Set response’s status message to `Partial Content`.
|
||||
response->set_status_message(MUST(ByteBuffer::copy("Partial Content"sv.bytes())));
|
||||
response->set_status_message("Partial Content"sv);
|
||||
|
||||
// 15. Set response’s header list to «
|
||||
|
||||
// (`Content-Length`, serializedSlicedLength),
|
||||
auto content_length_header = Infrastructure::Header::from_string_pair("Content-Length"sv, serialized_sliced_length);
|
||||
auto content_length_header = Infrastructure::Header::isomorphic_encode("Content-Length"sv, serialized_sliced_length);
|
||||
response->header_list()->append(move(content_length_header));
|
||||
|
||||
// (`Content-Type`, type),
|
||||
auto content_type_header = Infrastructure::Header::from_string_pair("Content-Type"sv, type);
|
||||
auto content_type_header = Infrastructure::Header::isomorphic_encode("Content-Type"sv, type);
|
||||
response->header_list()->append(move(content_type_header));
|
||||
|
||||
// (`Content-Range`, contentRange) ».
|
||||
auto content_range_header = Infrastructure::Header::from_string_pair("Content-Range"sv, content_range);
|
||||
auto content_range_header = Infrastructure::Header::isomorphic_encode("Content-Range"sv, content_range);
|
||||
response->header_list()->append(move(content_range_header));
|
||||
}
|
||||
|
||||
|
|
@ -1019,9 +1018,9 @@ GC::Ref<PendingResponse> scheme_fetch(JS::Realm& realm, Infrastructure::FetchPar
|
|||
// 4. Return a new response whose status message is `OK`, header list is « (`Content-Type`, mimeType) », and
|
||||
// body is dataURLStruct’s body as a body.
|
||||
auto response = Infrastructure::Response::create(vm);
|
||||
response->set_status_message(MUST(ByteBuffer::copy("OK"sv.bytes())));
|
||||
response->set_status_message("OK"sv);
|
||||
|
||||
auto header = Infrastructure::Header::from_string_pair("Content-Type"sv, mime_type);
|
||||
auto header = Infrastructure::Header::isomorphic_encode("Content-Type"sv, mime_type);
|
||||
response->header_list()->append(move(header));
|
||||
|
||||
response->set_body(Infrastructure::byte_sequence_as_body(realm, data_url_struct.value().body));
|
||||
|
|
@ -1334,13 +1333,13 @@ GC::Ptr<PendingResponse> http_redirect_fetch(JS::Realm& realm, Infrastructure::F
|
|||
// 12. If one of the following is true
|
||||
if (
|
||||
// - internalResponse’s status is 301 or 302 and request’s method is `POST`
|
||||
((internal_response->status() == 301 || internal_response->status() == 302) && request->method() == "POST"sv.bytes())
|
||||
((internal_response->status() == 301 || internal_response->status() == 302) && request->method() == "POST"sv)
|
||||
// - internalResponse’s status is 303 and request’s method is not `GET` or `HEAD`
|
||||
|| (internal_response->status() == 303 && !(request->method() == "GET"sv.bytes() || request->method() == "HEAD"sv.bytes()))
|
||||
|| (internal_response->status() == 303 && !(request->method() == "GET"sv || request->method() == "HEAD"sv))
|
||||
// then:
|
||||
) {
|
||||
// 1. Set request’s method to `GET` and request’s body to null.
|
||||
request->set_method(MUST(ByteBuffer::copy("GET"sv.bytes())));
|
||||
request->set_method("GET"sv);
|
||||
request->set_body({});
|
||||
|
||||
static constexpr Array request_body_header_names {
|
||||
|
|
@ -1351,7 +1350,7 @@ GC::Ptr<PendingResponse> http_redirect_fetch(JS::Realm& realm, Infrastructure::F
|
|||
};
|
||||
// 2. For each headerName of request-body-header name, delete headerName from request’s header list.
|
||||
for (auto header_name : request_body_header_names.span())
|
||||
request->header_list()->delete_(header_name.bytes());
|
||||
request->header_list()->delete_(header_name);
|
||||
}
|
||||
|
||||
// 13. If request’s current URL’s origin is not same origin with locationURL’s origin, then for each headerName of
|
||||
|
|
@ -1362,7 +1361,7 @@ GC::Ptr<PendingResponse> http_redirect_fetch(JS::Realm& realm, Infrastructure::F
|
|||
"Authorization"sv
|
||||
};
|
||||
for (auto header_name : cors_non_wildcard_request_header_names)
|
||||
request->header_list()->delete_(header_name.bytes());
|
||||
request->header_list()->delete_(header_name);
|
||||
}
|
||||
|
||||
// 14. If request’s body is non-null, then set request’s body to the body of the result of safely extracting
|
||||
|
|
@ -1417,7 +1416,7 @@ GC::Ptr<PendingResponse> http_redirect_fetch(JS::Realm& realm, Infrastructure::F
|
|||
class CachePartition : public RefCounted<CachePartition> {
|
||||
public:
|
||||
// https://httpwg.org/specs/rfc9111.html#constructing.responses.from.caches
|
||||
GC::Ptr<Infrastructure::Response> select_response(JS::Realm& realm, URL::URL const& url, ReadonlyBytes method, Vector<Infrastructure::Header> const& headers, Vector<GC::Ptr<Infrastructure::Response>>& initial_set_of_stored_responses) const
|
||||
GC::Ptr<Infrastructure::Response> select_response(JS::Realm& realm, URL::URL const& url, StringView method, Vector<Infrastructure::Header> const& headers, Vector<GC::Ptr<Infrastructure::Response>>& initial_set_of_stored_responses) const
|
||||
{
|
||||
// When presented with a request, a cache MUST NOT reuse a stored response unless:
|
||||
|
||||
|
|
@ -1461,7 +1460,7 @@ public:
|
|||
store_header_and_trailer_fields(response, *cached_response->header_list());
|
||||
cached_response->set_body(response.body()->clone(realm));
|
||||
cached_response->set_body_info(response.body_info());
|
||||
cached_response->set_method(MUST(ByteBuffer::copy(http_request.method())));
|
||||
cached_response->set_method(http_request.method());
|
||||
cached_response->set_status(response.status());
|
||||
cached_response->url_list().append(http_request.current_url());
|
||||
m_cache.set(http_request.current_url(), move(cached_response));
|
||||
|
|
@ -1570,35 +1569,23 @@ private:
|
|||
// https://httpwg.org/specs/rfc9111.html#update
|
||||
void update_stored_header_fields(Infrastructure::Response const& response, Infrastructure::HeaderList& headers)
|
||||
{
|
||||
for (auto& header : *response.header_list()) {
|
||||
auto name = StringView(header.name);
|
||||
|
||||
if (is_exempted_for_updating(name))
|
||||
continue;
|
||||
|
||||
headers.delete_(header.name);
|
||||
for (auto const& header : *response.header_list()) {
|
||||
if (!is_exempted_for_updating(header.name))
|
||||
headers.delete_(header.name);
|
||||
}
|
||||
|
||||
for (auto& header : *response.header_list()) {
|
||||
auto name = StringView(header.name);
|
||||
|
||||
if (is_exempted_for_updating(name))
|
||||
continue;
|
||||
|
||||
headers.append(Infrastructure::Header::copy(header));
|
||||
for (auto const& header : *response.header_list()) {
|
||||
if (!is_exempted_for_updating(header.name))
|
||||
headers.append(header);
|
||||
}
|
||||
}
|
||||
|
||||
// https://httpwg.org/specs/rfc9111.html#storing.fields
|
||||
void store_header_and_trailer_fields(Infrastructure::Response const& response, Web::Fetch::Infrastructure::HeaderList& headers)
|
||||
{
|
||||
for (auto& header : *response.header_list()) {
|
||||
auto name = StringView(header.name);
|
||||
|
||||
if (is_exempted_for_storage(name))
|
||||
continue;
|
||||
|
||||
headers.append(Infrastructure::Header::copy(header));
|
||||
for (auto const& header : *response.header_list()) {
|
||||
if (!is_exempted_for_storage(header.name))
|
||||
headers.append(header);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1612,7 +1599,7 @@ private:
|
|||
return false;
|
||||
|
||||
// - the request method is understood by the cache;
|
||||
if (request.method() != "GET"sv.bytes() && request.method() != "HEAD"sv.bytes())
|
||||
if (request.method() != "GET"sv && request.method() != "HEAD"sv)
|
||||
return false;
|
||||
|
||||
// - the response status code is final (see Section 15 of [HTTP]);
|
||||
|
|
@ -1787,29 +1774,22 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
: Optional<u64> {};
|
||||
|
||||
// 6. Let contentLengthHeaderValue be null.
|
||||
auto content_length_header_value = Optional<ByteBuffer> {};
|
||||
Optional<ByteString> content_length_header_value;
|
||||
|
||||
// 7. If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`, then set
|
||||
// contentLengthHeaderValue to `0`.
|
||||
if (http_request->body().has<Empty>() && StringView { http_request->method() }.is_one_of("POST"sv, "PUT"sv))
|
||||
content_length_header_value = MUST(ByteBuffer::copy("0"sv.bytes()));
|
||||
if (http_request->body().has<Empty>() && http_request->method().is_one_of("POST"sv, "PUT"sv))
|
||||
content_length_header_value = "0"sv;
|
||||
|
||||
// 8. If contentLength is non-null, then set contentLengthHeaderValue to contentLength, serialized and
|
||||
// isomorphic encoded.
|
||||
if (content_length.has_value()) {
|
||||
auto content_length_string = String::number(*content_length);
|
||||
content_length_header_value = MUST(ByteBuffer::copy(content_length_string.bytes()));
|
||||
}
|
||||
if (content_length.has_value())
|
||||
content_length_header_value = ByteString::number(*content_length);
|
||||
|
||||
// 9. If contentLengthHeaderValue is non-null, then append (`Content-Length`, contentLengthHeaderValue) to
|
||||
// httpRequest’s header list.
|
||||
if (content_length_header_value.has_value()) {
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Content-Length"sv.bytes())),
|
||||
.value = content_length_header_value.release_value(),
|
||||
};
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
if (content_length_header_value.has_value())
|
||||
http_request->header_list()->append({ "Content-Length"sv, content_length_header_value.release_value() });
|
||||
|
||||
// 10. If contentLength is non-null and httpRequest’s keepalive is true, then:
|
||||
if (content_length.has_value() && http_request->keepalive()) {
|
||||
|
|
@ -1849,17 +1829,12 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
}
|
||||
|
||||
// 11. If httpRequest’s referrer is a URL, then:
|
||||
if (http_request->referrer().has<URL::URL>()) {
|
||||
if (auto const* referrer_url = http_request->referrer().get_pointer<URL::URL>()) {
|
||||
// 1. Let referrerValue be httpRequest’s referrer, serialized and isomorphic encoded.
|
||||
auto referrer_string = http_request->referrer().get<URL::URL>().serialize();
|
||||
auto referrer_value = MUST(ByteBuffer::copy(referrer_string.bytes()));
|
||||
auto referrer_value = Infra::isomorphic_encode(referrer_url->serialize());
|
||||
|
||||
// 2. Append (`Referer`, referrerValue) to httpRequest’s header list.
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Referer"sv.bytes())),
|
||||
.value = move(referrer_value),
|
||||
};
|
||||
http_request->header_list()->append(move(header));
|
||||
http_request->header_list()->append({ "Referer"sv, move(referrer_value) });
|
||||
}
|
||||
|
||||
// 12. Append a request `Origin` header for httpRequest.
|
||||
|
|
@ -1873,23 +1848,18 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
|
||||
// 15. If httpRequest’s header list does not contain `User-Agent`, then user agents should append
|
||||
// (`User-Agent`, default `User-Agent` value) to httpRequest’s header list.
|
||||
if (!http_request->header_list()->contains("User-Agent"sv.bytes())) {
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("User-Agent"sv.bytes())),
|
||||
.value = Infrastructure::default_user_agent_value(),
|
||||
};
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
if (!http_request->header_list()->contains("User-Agent"sv))
|
||||
http_request->header_list()->append({ "User-Agent"sv, Infrastructure::default_user_agent_value() });
|
||||
|
||||
// 16. If httpRequest’s cache mode is "default" and httpRequest’s header list contains `If-Modified-Since`,
|
||||
// `If-None-Match`, `If-Unmodified-Since`, `If-Match`, or `If-Range`, then set httpRequest’s cache mode to
|
||||
// "no-store".
|
||||
if (http_request->cache_mode() == Infrastructure::Request::CacheMode::Default
|
||||
&& (http_request->header_list()->contains("If-Modified-Since"sv.bytes())
|
||||
|| http_request->header_list()->contains("If-None-Match"sv.bytes())
|
||||
|| http_request->header_list()->contains("If-Unmodified-Since"sv.bytes())
|
||||
|| http_request->header_list()->contains("If-Match"sv.bytes())
|
||||
|| http_request->header_list()->contains("If-Range"sv.bytes()))) {
|
||||
&& (http_request->header_list()->contains("If-Modified-Since"sv)
|
||||
|| http_request->header_list()->contains("If-None-Match"sv)
|
||||
|| http_request->header_list()->contains("If-Unmodified-Since"sv)
|
||||
|| http_request->header_list()->contains("If-Match"sv)
|
||||
|| http_request->header_list()->contains("If-Range"sv))) {
|
||||
http_request->set_cache_mode(Infrastructure::Request::CacheMode::NoStore);
|
||||
}
|
||||
|
||||
|
|
@ -1898,9 +1868,8 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
// (`Cache-Control`, `max-age=0`) to httpRequest’s header list.
|
||||
if (http_request->cache_mode() == Infrastructure::Request::CacheMode::NoCache
|
||||
&& !http_request->prevent_no_cache_cache_control_header_modification()
|
||||
&& !http_request->header_list()->contains("Cache-Control"sv.bytes())) {
|
||||
auto header = Infrastructure::Header::from_string_pair("Cache-Control"sv, "max-age=0"sv);
|
||||
http_request->header_list()->append(move(header));
|
||||
&& !http_request->header_list()->contains("Cache-Control"sv)) {
|
||||
http_request->header_list()->append({ "Cache-Control"sv, "max-age=0"sv });
|
||||
}
|
||||
|
||||
// 18. If httpRequest’s cache mode is "no-store" or "reload", then:
|
||||
|
|
@ -1908,27 +1877,21 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
|| http_request->cache_mode() == Infrastructure::Request::CacheMode::Reload) {
|
||||
// 1. If httpRequest’s header list does not contain `Pragma`, then append (`Pragma`, `no-cache`) to
|
||||
// httpRequest’s header list.
|
||||
if (!http_request->header_list()->contains("Pragma"sv.bytes())) {
|
||||
auto header = Infrastructure::Header::from_string_pair("Pragma"sv, "no-cache"sv);
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
if (!http_request->header_list()->contains("Pragma"sv))
|
||||
http_request->header_list()->append({ "Pragma"sv, "no-cache"sv });
|
||||
|
||||
// 2. If httpRequest’s header list does not contain `Cache-Control`, then append
|
||||
// (`Cache-Control`, `no-cache`) to httpRequest’s header list.
|
||||
if (!http_request->header_list()->contains("Cache-Control"sv.bytes())) {
|
||||
auto header = Infrastructure::Header::from_string_pair("Cache-Control"sv, "no-cache"sv);
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
if (!http_request->header_list()->contains("Cache-Control"sv))
|
||||
http_request->header_list()->append({ "Cache-Control"sv, "no-cache"sv });
|
||||
}
|
||||
|
||||
// 19. If httpRequest’s header list contains `Range`, then append (`Accept-Encoding`, `identity`) to
|
||||
// httpRequest’s header list.
|
||||
// NOTE: This avoids a failure when handling content codings with a part of an encoded response.
|
||||
// Additionally, many servers mistakenly ignore `Range` headers if a non-identity encoding is accepted.
|
||||
if (http_request->header_list()->contains("Range"sv.bytes())) {
|
||||
auto header = Infrastructure::Header::from_string_pair("Accept-Encoding"sv, "identity"sv);
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
if (http_request->header_list()->contains("Range"sv))
|
||||
http_request->header_list()->append({ "Accept-Encoding"sv, "identity"sv });
|
||||
|
||||
// 20. Modify httpRequest’s header list per HTTP. Do not append a given header if httpRequest’s header list
|
||||
// contains that header’s name.
|
||||
|
|
@ -1940,10 +1903,8 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
// more details.
|
||||
//
|
||||
// https://w3c.github.io/gpc/#the-sec-gpc-header-field-for-http-requests
|
||||
if (ResourceLoader::the().enable_global_privacy_control() && !http_request->header_list()->contains("Sec-GPC"sv.bytes())) {
|
||||
auto header = Infrastructure::Header::from_string_pair("Sec-GPC"sv, "1"sv);
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
if (ResourceLoader::the().enable_global_privacy_control() && !http_request->header_list()->contains("Sec-GPC"sv))
|
||||
http_request->header_list()->append({ "Sec-GPC"sv, "1"sv });
|
||||
|
||||
// 21. If includeCredentials is true, then:
|
||||
if (include_credentials == IncludeCredentials::Yes) {
|
||||
|
|
@ -1959,13 +1920,13 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
|
||||
// 2. If cookies is not the empty string, then append (`Cookie`, cookies) to httpRequest’s header list.
|
||||
if (!cookies.is_empty()) {
|
||||
auto header = Infrastructure::Header::from_string_pair("Cookie"sv, cookies);
|
||||
auto header = Infrastructure::Header::isomorphic_encode("Cookie"sv, cookies);
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If httpRequest’s header list does not contain `Authorization`, then:
|
||||
if (!http_request->header_list()->contains("Authorization"sv.bytes())) {
|
||||
if (!http_request->header_list()->contains("Authorization"sv)) {
|
||||
// 1. Let authorizationValue be null.
|
||||
auto authorization_value = Optional<String> {};
|
||||
|
||||
|
|
@ -1987,7 +1948,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
// 4. If authorizationValue is non-null, then append (`Authorization`, authorizationValue) to
|
||||
// httpRequest’s header list.
|
||||
if (authorization_value.has_value()) {
|
||||
auto header = Infrastructure::Header::from_string_pair("Authorization"sv, *authorization_value);
|
||||
auto header = Infrastructure::Header::isomorphic_encode("Authorization"sv, *authorization_value);
|
||||
http_request->header_list()->append(move(header));
|
||||
}
|
||||
}
|
||||
|
|
@ -2011,6 +1972,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
// if any.
|
||||
// NOTE: As mandated by HTTP, this still takes the `Vary` header into account.
|
||||
stored_response = http_cache->select_response(realm, http_request->current_url(), http_request->method(), *http_request->header_list(), initial_set_of_stored_responses);
|
||||
|
||||
// 2. If storedResponse is non-null, then:
|
||||
if (stored_response) {
|
||||
// 1. If cache mode is "default", storedResponse is a stale-while-revalidate response,
|
||||
|
|
@ -2054,13 +2016,13 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
&& http_request->cache_mode() != Infrastructure::Request::CacheMode::OnlyIfCached) {
|
||||
|
||||
// 1. If storedResponse’s header list contains `ETag`, then append (`If-None-Match`, `ETag`'s value) to httpRequest’s header list.
|
||||
if (auto etag = stored_response->header_list()->get("ETag"sv.bytes()); etag.has_value()) {
|
||||
http_request->header_list()->append(Infrastructure::Header::from_string_pair("If-None-Match"sv, *etag));
|
||||
if (auto etag = stored_response->header_list()->get("ETag"sv); etag.has_value()) {
|
||||
http_request->header_list()->append(Infrastructure::Header::isomorphic_encode("If-None-Match"sv, *etag));
|
||||
}
|
||||
|
||||
// 2. If storedResponse’s header list contains `Last-Modified`, then append (`If-Modified-Since`, `Last-Modified`'s value) to httpRequest’s header list.
|
||||
if (auto last_modified = stored_response->header_list()->get("Last-Modified"sv.bytes()); last_modified.has_value()) {
|
||||
http_request->header_list()->append(Infrastructure::Header::from_string_pair("If-Modified-Since"sv, *last_modified));
|
||||
if (auto last_modified = stored_response->header_list()->get("Last-Modified"sv); last_modified.has_value()) {
|
||||
http_request->header_list()->append(Infrastructure::Header::isomorphic_encode("If-Modified-Since"sv, *last_modified));
|
||||
}
|
||||
}
|
||||
// 3. Otherwise, set response to storedResponse and set response’s cache state to "local".
|
||||
|
|
@ -2100,7 +2062,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
auto forward_response = resolved_forward_response;
|
||||
|
||||
// NOTE: TRACE is omitted as it is a forbidden method in Fetch.
|
||||
auto method_is_unsafe = !(StringView { http_request->method() }.is_one_of("GET"sv, "HEAD"sv, "OPTIONS"sv));
|
||||
auto method_is_unsafe = !http_request->method().is_one_of("GET"sv, "HEAD"sv, "OPTIONS"sv);
|
||||
|
||||
// 3. If httpRequest’s method is unsafe and forwardResponse’s status is in the range 200 to 399, inclusive,
|
||||
// invalidate appropriate stored responses in httpCache, as per the "Invalidation" chapter of HTTP
|
||||
|
|
@ -2145,7 +2107,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
response->set_url_list(http_request->url_list());
|
||||
|
||||
// 12. If httpRequest’s header list contains `Range`, then set response’s range-requested flag.
|
||||
if (http_request->header_list()->contains("Range"sv.bytes()))
|
||||
if (http_request->header_list()->contains("Range"sv))
|
||||
response->set_range_requested(true);
|
||||
|
||||
// 13. Set response’s request-includes-credentials to includeCredentials.
|
||||
|
|
@ -2161,7 +2123,7 @@ GC::Ref<PendingResponse> http_network_or_cache_fetch(JS::Realm& realm, Infrastru
|
|||
&& request->traversable_for_user_prompts().has<GC::Ptr<HTML::TraversableNavigable>>()
|
||||
// AD-HOC: Require at least one WWW-Authenticate header to be set before automatically retrying an authenticated
|
||||
// request (see rule 1 below). See: https://github.com/whatwg/fetch/issues/1766
|
||||
&& request->header_list()->contains("WWW-Authenticate"sv.bytes())) {
|
||||
&& request->header_list()->contains("WWW-Authenticate"sv)) {
|
||||
// 1. Needs testing: multiple `WWW-Authenticate` headers, missing, parsing issues.
|
||||
// (Red box in the spec, no-op)
|
||||
|
||||
|
|
@ -2324,11 +2286,11 @@ GC::Ref<PendingResponse> nonstandard_resource_loader_file_or_http_network_fetch(
|
|||
LoadRequest load_request;
|
||||
load_request.set_url(request->current_url());
|
||||
load_request.set_page(page);
|
||||
load_request.set_method(ByteString::copy(request->method()));
|
||||
load_request.set_method(request->method());
|
||||
load_request.set_store_set_cookie_headers(include_credentials == IncludeCredentials::Yes);
|
||||
|
||||
for (auto const& header : *request->header_list())
|
||||
load_request.set_header(ByteString::copy(header.name), ByteString::copy(header.value));
|
||||
load_request.set_header(header.name, header.value);
|
||||
|
||||
if (auto const* body = request->body().get_pointer<GC::Ref<Infrastructure::Body>>()) {
|
||||
(*body)->source().visit(
|
||||
|
|
@ -2390,7 +2352,7 @@ GC::Ref<PendingResponse> nonstandard_resource_loader_file_or_http_network_fetch(
|
|||
response->set_status(status_code.value_or(200));
|
||||
|
||||
if (reason_phrase.has_value())
|
||||
response->set_status_message(MUST(ByteBuffer::copy(reason_phrase.value().bytes())));
|
||||
response->set_status_message(reason_phrase->to_byte_string());
|
||||
|
||||
(void)request;
|
||||
if constexpr (WEB_FETCH_DEBUG) {
|
||||
|
|
@ -2401,10 +2363,8 @@ GC::Ref<PendingResponse> nonstandard_resource_loader_file_or_http_network_fetch(
|
|||
log_response(status_code, response_headers, ReadonlyBytes {});
|
||||
}
|
||||
|
||||
for (auto const& [name, value] : response_headers.headers()) {
|
||||
auto header = Infrastructure::Header::from_latin1_pair(name, value);
|
||||
response->header_list()->append(move(header));
|
||||
}
|
||||
for (auto const& [name, value] : response_headers.headers())
|
||||
response->header_list()->append({ name, value });
|
||||
|
||||
// 14. Set response’s body to a new body whose stream is stream.
|
||||
response->set_body(Infrastructure::Body::create(vm, stream));
|
||||
|
|
@ -2454,7 +2414,7 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
// request’s initiator, destination is request’s destination, origin is request’s origin, referrer is request’s referrer,
|
||||
// referrer policy is request’s referrer policy, mode is "cors", and response tainting is "cors".
|
||||
auto preflight = Fetch::Infrastructure::Request::create(vm);
|
||||
preflight->set_method(MUST(ByteBuffer::copy("OPTIONS"sv.bytes())));
|
||||
preflight->set_method("OPTIONS"sv);
|
||||
preflight->set_url_list(request.url_list());
|
||||
preflight->set_initiator(request.initiator());
|
||||
preflight->set_destination(request.destination());
|
||||
|
|
@ -2465,11 +2425,10 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
preflight->set_response_tainting(Infrastructure::Request::ResponseTainting::CORS);
|
||||
|
||||
// 2. Append (`Accept`, `*/*`) to preflight’s header list.
|
||||
auto temp_header = Infrastructure::Header::from_string_pair("Accept"sv, "*/*"sv);
|
||||
preflight->header_list()->append(move(temp_header));
|
||||
preflight->header_list()->append({ "Accept"sv, "*/*"sv });
|
||||
|
||||
// 3. Append (`Access-Control-Request-Method`, request’s method) to preflight’s header list.
|
||||
temp_header = Infrastructure::Header::from_string_pair("Access-Control-Request-Method"sv, request.method());
|
||||
auto temp_header = Infrastructure::Header::isomorphic_encode("Access-Control-Request-Method"sv, request.method());
|
||||
preflight->header_list()->append(move(temp_header));
|
||||
|
||||
// 4. Let headers be the CORS-unsafe request-header names with request’s header list.
|
||||
|
|
@ -2480,22 +2439,10 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
// 1. Let value be the items in headers separated from each other by `,`.
|
||||
// NOTE: This intentionally does not use combine, as 0x20 following 0x2C is not the way this was implemented,
|
||||
// for better or worse.
|
||||
ByteBuffer value;
|
||||
|
||||
bool first = true;
|
||||
for (auto const& header : headers) {
|
||||
if (!first)
|
||||
value.append(',');
|
||||
value.append(header);
|
||||
first = false;
|
||||
}
|
||||
auto value = ByteString::join(',', headers);
|
||||
|
||||
// 2. Append (`Access-Control-Request-Headers`, value) to preflight’s header list.
|
||||
temp_header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Access-Control-Request-Headers"sv.bytes())),
|
||||
.value = move(value),
|
||||
};
|
||||
preflight->header_list()->append(move(temp_header));
|
||||
preflight->header_list()->append({ "Access-Control-Request-Headers"sv, move(value) });
|
||||
}
|
||||
|
||||
// 6. Let response be the result of running HTTP-network-or-cache fetch given a new fetch params whose request is preflight.
|
||||
|
|
@ -2514,18 +2461,17 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
// NOTE: The CORS check is done on request rather than preflight to ensure the correct credentials mode is used.
|
||||
if (cors_check(request, response) && Infrastructure::is_ok_status(response->status())) {
|
||||
// 1. Let methods be the result of extracting header list values given `Access-Control-Allow-Methods` and response’s header list.
|
||||
auto methods_or_failure = response->header_list()->extract_header_list_values("Access-Control-Allow-Methods"sv.bytes());
|
||||
auto methods_or_failure = response->header_list()->extract_header_list_values("Access-Control-Allow-Methods"sv);
|
||||
|
||||
// 2. Let headerNames be the result of extracting header list values given `Access-Control-Allow-Headers` and
|
||||
// response’s header list.
|
||||
auto header_names_or_failure = response->header_list()->extract_header_list_values("Access-Control-Allow-Headers"sv.bytes());
|
||||
auto header_names_or_failure = response->header_list()->extract_header_list_values("Access-Control-Allow-Headers"sv);
|
||||
|
||||
// 3. If either methods or headerNames is failure, return a network error.
|
||||
if (methods_or_failure.has<Infrastructure::HeaderList::ExtractHeaderParseFailure>()) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, "The Access-Control-Allow-Methods in the CORS-preflight response is syntactically invalid"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
if (header_names_or_failure.has<Infrastructure::HeaderList::ExtractHeaderParseFailure>()) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, "The Access-Control-Allow-Headers in the CORS-preflight response is syntactically invalid"_string));
|
||||
return;
|
||||
|
|
@ -2533,29 +2479,29 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
|
||||
// NOTE: We treat "methods_or_failure" being `Empty` as empty Vector here.
|
||||
auto methods = methods_or_failure.visit(
|
||||
[](Vector<ByteBuffer>& methods) { return move(methods); },
|
||||
[](auto) -> Vector<ByteBuffer> { return {}; });
|
||||
[](Vector<ByteString>& methods) { return move(methods); },
|
||||
[](auto) -> Vector<ByteString> { return {}; });
|
||||
|
||||
// NOTE: We treat "header_names_or_failure" being `Empty` as empty Vector here.
|
||||
auto header_names = header_names_or_failure.visit(
|
||||
[](Vector<ByteBuffer>& header_names) { return move(header_names); },
|
||||
[](auto) -> Vector<ByteBuffer> { return {}; });
|
||||
[](Vector<ByteString>& header_names) { return move(header_names); },
|
||||
[](auto) -> Vector<ByteString> { return {}; });
|
||||
|
||||
// 4. If methods is null and request’s use-CORS-preflight flag is set, then set methods to a new list containing request’s method.
|
||||
// NOTE: This ensures that a CORS-preflight fetch that happened due to request’s use-CORS-preflight flag being set is cached.
|
||||
if (methods.is_empty() && request.use_cors_preflight())
|
||||
methods = Vector { TRY_OR_IGNORE(ByteBuffer::copy(request.method())) };
|
||||
methods = { request.method() };
|
||||
|
||||
// 5. If request’s method is not in methods, request’s method is not a CORS-safelisted method, and request’s credentials mode
|
||||
// is "include" or methods does not contain `*`, then return a network error.
|
||||
if (!methods.contains_slow(request.method()) && !Infrastructure::is_cors_safelisted_method(request.method())) {
|
||||
if (request.credentials_mode() == Infrastructure::Request::CredentialsMode::Include) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("Non-CORS-safelisted method '{}' not found in the CORS-preflight response's Access-Control-Allow-Methods header (the header may be missing). '*' is not allowed as the main request includes credentials.", StringView { request.method() }))));
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("Non-CORS-safelisted method '{}' not found in the CORS-preflight response's Access-Control-Allow-Methods header (the header may be missing). '*' is not allowed as the main request includes credentials.", request.method()))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!methods.contains_slow("*"sv.bytes())) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("Non-CORS-safelisted method '{}' not found in the CORS-preflight response's Access-Control-Allow-Methods header and there was no '*' entry. The header may be missing.", StringView { request.method() }))));
|
||||
if (!methods.contains_slow("*"sv)) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("Non-CORS-safelisted method '{}' not found in the CORS-preflight response's Access-Control-Allow-Methods header and there was no '*' entry. The header may be missing.", request.method()))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -2567,14 +2513,14 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
bool is_in_header_names = false;
|
||||
|
||||
for (auto const& allowed_header_name : header_names) {
|
||||
if (StringView { allowed_header_name }.equals_ignoring_ascii_case(header.name)) {
|
||||
if (allowed_header_name.equals_ignoring_ascii_case(header.name)) {
|
||||
is_in_header_names = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_in_header_names) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("Main request contains the header '{}' that is not specified in the CORS-preflight response's Access-Control-Allow-Headers header (the header may be missing). '*' does not capture this header.", StringView { header.name }))));
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("Main request contains the header '{}' that is not specified in the CORS-preflight response's Access-Control-Allow-Headers header (the header may be missing). '*' does not capture this header.", header.name))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -2588,7 +2534,7 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
bool is_in_header_names = false;
|
||||
|
||||
for (auto const& header_name : header_names) {
|
||||
if (StringView { unsafe_name }.equals_ignoring_ascii_case(header_name)) {
|
||||
if (unsafe_name.equals_ignoring_ascii_case(header_name)) {
|
||||
is_in_header_names = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2596,12 +2542,12 @@ GC::Ref<PendingResponse> cors_preflight_fetch(JS::Realm& realm, Infrastructure::
|
|||
|
||||
if (!is_in_header_names) {
|
||||
if (request.credentials_mode() == Infrastructure::Request::CredentialsMode::Include) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("CORS-unsafe request-header '{}' not found in the CORS-preflight response's Access-Control-Allow-Headers header (the header may be missing). '*' is not allowed as the main request includes credentials.", StringView { unsafe_name }))));
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("CORS-unsafe request-header '{}' not found in the CORS-preflight response's Access-Control-Allow-Headers header (the header may be missing). '*' is not allowed as the main request includes credentials.", unsafe_name))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!header_names.contains_slow("*"sv.bytes())) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("CORS-unsafe request-header '{}' not found in the CORS-preflight response's Access-Control-Allow-Headers header and there was no '*' entry. The header may be missing.", StringView { unsafe_name }))));
|
||||
if (!header_names.contains_slow("*"sv)) {
|
||||
returned_pending_response->resolve(Infrastructure::Response::network_error(vm, TRY_OR_IGNORE(String::formatted("CORS-unsafe request-header '{}' not found in the CORS-preflight response's Access-Control-Allow-Headers header and there was no '*' entry. The header may be missing.", unsafe_name))));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -2644,19 +2590,12 @@ void set_sec_fetch_dest_header(Infrastructure::Request& request)
|
|||
// FIXME: This is handled below, as Serenity doesn't have APIs for RFC 8941.
|
||||
|
||||
// 3. If r’s destination is the empty string, set header’s value to the string "empty". Otherwise, set header’s value to r’s destination.
|
||||
ByteBuffer header_value;
|
||||
if (!request.destination().has_value()) {
|
||||
header_value = MUST(ByteBuffer::copy("empty"sv.bytes()));
|
||||
} else {
|
||||
header_value = MUST(ByteBuffer::copy(Infrastructure::request_destination_to_string(request.destination().value()).bytes()));
|
||||
}
|
||||
auto value = request.destination().has_value()
|
||||
? Infrastructure::request_destination_to_string(*request.destination())
|
||||
: "empty"sv;
|
||||
|
||||
// 4. Set a structured field value `Sec-Fetch-Dest`/header in r’s header list.
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Sec-Fetch-Dest"sv.bytes())),
|
||||
.value = move(header_value),
|
||||
};
|
||||
request.header_list()->append(move(header));
|
||||
request.header_list()->append({ "Sec-Fetch-Dest"sv, value });
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-dest
|
||||
|
|
@ -2669,14 +2608,10 @@ void set_sec_fetch_mode_header(Infrastructure::Request& request)
|
|||
// FIXME: This is handled below, as Serenity doesn't have APIs for RFC 8941.
|
||||
|
||||
// 3. Set header’s value to r’s mode.
|
||||
auto header_value = MUST(ByteBuffer::copy(Infrastructure::request_mode_to_string(request.mode()).bytes()));
|
||||
auto value = Infrastructure::request_mode_to_string(request.mode());
|
||||
|
||||
// 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Sec-Fetch-Mode"sv.bytes())),
|
||||
.value = move(header_value),
|
||||
};
|
||||
request.header_list()->append(move(header));
|
||||
request.header_list()->append({ "Sec-Fetch-Mode"sv, value });
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-site
|
||||
|
|
@ -2689,39 +2624,35 @@ void set_sec_fetch_site_header(Infrastructure::Request& request)
|
|||
// FIXME: This is handled below, as Serenity doesn't have APIs for RFC 8941.
|
||||
|
||||
// 3. Set header’s value to same-origin.
|
||||
auto header_value = "same-origin"sv;
|
||||
auto value = "same-origin"sv;
|
||||
|
||||
// FIXME: 4. If r is a navigation request that was explicitly caused by a user’s interaction with the user agent (by typing an address
|
||||
// into the user agent directly, for example, or by clicking a bookmark, etc.), then set header’s value to none.
|
||||
|
||||
// 5. If header’s value is not none, then for each url in r’s url list:
|
||||
if (!header_value.equals_ignoring_ascii_case("none"sv)) {
|
||||
if (!value.equals_ignoring_ascii_case("none"sv)) {
|
||||
VERIFY(request.origin().has<URL::Origin>());
|
||||
auto& request_origin = request.origin().get<URL::Origin>();
|
||||
auto const& request_origin = request.origin().get<URL::Origin>();
|
||||
|
||||
for (auto& url : request.url_list()) {
|
||||
for (auto const& url : request.url_list()) {
|
||||
// 1. If url is same origin with r’s origin, continue.
|
||||
if (url.origin().is_same_origin(request_origin))
|
||||
continue;
|
||||
|
||||
// 2. Set header’s value to cross-site.
|
||||
header_value = "cross-site"sv;
|
||||
value = "cross-site"sv;
|
||||
|
||||
// 3. If r’s origin is not same site with url’s origin, then break.
|
||||
if (!request_origin.is_same_site(url.origin()))
|
||||
break;
|
||||
|
||||
// 4. Set header’s value to same-site.
|
||||
header_value = "same-site"sv;
|
||||
value = "same-site"sv;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Set a structured field value `Sec-Fetch-Site`/header in r’s header list.
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Sec-Fetch-Site"sv.bytes())),
|
||||
.value = MUST(ByteBuffer::copy(header_value.bytes())),
|
||||
};
|
||||
request.header_list()->append(move(header));
|
||||
request.header_list()->append({ "Sec-Fetch-Site"sv, value });
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-user
|
||||
|
|
@ -2739,14 +2670,10 @@ void set_sec_fetch_user_header(Infrastructure::Request& request)
|
|||
|
||||
// 4. Set header’s value to true.
|
||||
// NOTE: See https://datatracker.ietf.org/doc/html/rfc8941#name-booleans for boolean format in RFC 8941.
|
||||
auto header_value = MUST(ByteBuffer::copy("?1"sv.bytes()));
|
||||
static ByteString value = "?1"sv;
|
||||
|
||||
// 5. Set a structured field value `Sec-Fetch-User`/header in r’s header list.
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Sec-Fetch-User"sv.bytes())),
|
||||
.value = move(header_value),
|
||||
};
|
||||
request.header_list()->append(move(header));
|
||||
request.header_list()->append({ "Sec-Fetch-User"sv, value });
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-append-the-fetch-metadata-headers-for-a-request
|
||||
|
|
|
|||
|
|
@ -57,20 +57,19 @@ 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::from_string_pair(name_string, value_string);
|
||||
auto header = Infrastructure::Header::isomorphic_encode(name_string, value_string);
|
||||
TRY(append(move(header)));
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-delete
|
||||
WebIDL::ExceptionOr<void> Headers::delete_(String const& name_string)
|
||||
WebIDL::ExceptionOr<void> Headers::delete_(String const& name)
|
||||
{
|
||||
// The delete(name) method steps are:
|
||||
auto name = name_string.bytes();
|
||||
|
||||
// 1. If validating (name, ``) for headers returns false, then return.
|
||||
// NOTE: Passing a dummy header value ought not to have any negative repercussions.
|
||||
auto header = Infrastructure::Header::from_string_pair(name, ""sv);
|
||||
auto header = Infrastructure::Header::isomorphic_encode(name, ""sv);
|
||||
if (!TRY(validate(header)))
|
||||
return {};
|
||||
|
||||
|
|
@ -93,10 +92,9 @@ WebIDL::ExceptionOr<void> Headers::delete_(String const& name_string)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-get
|
||||
WebIDL::ExceptionOr<Optional<String>> Headers::get(String const& name_string)
|
||||
WebIDL::ExceptionOr<Optional<String>> Headers::get(String const& name)
|
||||
{
|
||||
// The get(name) method steps are:
|
||||
auto name = name_string.bytes();
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!Infrastructure::is_header_name(name))
|
||||
|
|
@ -114,23 +112,22 @@ Vector<String> Headers::get_set_cookie()
|
|||
auto values = Vector<String> {};
|
||||
|
||||
// 1. If this’s header list does not contain `Set-Cookie`, then return « ».
|
||||
if (!m_header_list->contains("Set-Cookie"sv.bytes()))
|
||||
if (!m_header_list->contains("Set-Cookie"sv))
|
||||
return values;
|
||||
|
||||
// 2. Return the values of all headers in this’s header list whose name is a byte-case-insensitive match for
|
||||
// `Set-Cookie`, in order.
|
||||
for (auto const& header : *m_header_list) {
|
||||
if (StringView { header.name }.equals_ignoring_ascii_case("Set-Cookie"sv))
|
||||
if (header.name.equals_ignoring_ascii_case("Set-Cookie"sv))
|
||||
values.append(Infra::isomorphic_decode(header.value));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-has
|
||||
WebIDL::ExceptionOr<bool> Headers::has(String const& name_string)
|
||||
WebIDL::ExceptionOr<bool> Headers::has(String const& name)
|
||||
{
|
||||
// The has(name) method steps are:
|
||||
auto name = name_string.bytes();
|
||||
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
if (!Infrastructure::is_header_name(name))
|
||||
|
|
@ -141,16 +138,14 @@ WebIDL::ExceptionOr<bool> Headers::has(String const& name_string)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-set
|
||||
WebIDL::ExceptionOr<void> Headers::set(String const& name_string, String const& value_string)
|
||||
WebIDL::ExceptionOr<void> Headers::set(String const& name, String const& value)
|
||||
{
|
||||
// The set(name, value) method steps are:
|
||||
auto name = name_string.bytes();
|
||||
auto value = value_string.bytes();
|
||||
|
||||
// 1. Normalize value.
|
||||
auto normalized_value = Infrastructure::normalize_header_value(value);
|
||||
|
||||
auto header = Infrastructure::Header::from_string_pair(name, normalized_value);
|
||||
auto header = Infrastructure::Header::isomorphic_encode(name, normalized_value);
|
||||
|
||||
// 2. If validating (name, value) for headers returns false, then return.
|
||||
if (!TRY(validate(header)))
|
||||
|
|
@ -252,17 +247,15 @@ WebIDL::ExceptionOr<void> Headers::append(Infrastructure::Header header)
|
|||
|
||||
// 2. If temporaryValue is null, then set temporaryValue to value.
|
||||
if (!temporary_value.has_value()) {
|
||||
temporary_value = MUST(ByteBuffer::copy(value));
|
||||
temporary_value = move(value);
|
||||
}
|
||||
// 3. Otherwise, set temporaryValue to temporaryValue, followed by 0x2C 0x20, followed by value.
|
||||
else {
|
||||
temporary_value->append(0x2c);
|
||||
temporary_value->append(0x20);
|
||||
temporary_value->append(value);
|
||||
temporary_value = ByteString::formatted("{}, {}", *temporary_value, value);
|
||||
}
|
||||
|
||||
auto temporary_header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy(name)),
|
||||
.name = move(name),
|
||||
.value = temporary_value.release_value(),
|
||||
};
|
||||
|
||||
|
|
@ -294,7 +287,7 @@ WebIDL::ExceptionOr<void> Headers::fill(HeadersInit const& object)
|
|||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Array must contain header key/value pair"sv };
|
||||
|
||||
// 2. Append (header[0], header[1]) to headers.
|
||||
auto header = Infrastructure::Header::from_string_pair(entry[0], entry[1]);
|
||||
auto header = Infrastructure::Header::isomorphic_encode(entry[0], entry[1]);
|
||||
TRY(append(move(header)));
|
||||
}
|
||||
return {};
|
||||
|
|
@ -302,7 +295,7 @@ WebIDL::ExceptionOr<void> Headers::fill(HeadersInit const& object)
|
|||
// 2. Otherwise, object is a record, then for each key → value of object, append (key, value) to headers.
|
||||
[&](OrderedHashMap<String, String> const& object) -> WebIDL::ExceptionOr<void> {
|
||||
for (auto const& entry : object) {
|
||||
auto header = Infrastructure::Header::from_string_pair(entry.key, entry.value);
|
||||
auto header = Infrastructure::Header::isomorphic_encode(entry.key, entry.value);
|
||||
TRY(append(move(header)));
|
||||
}
|
||||
return {};
|
||||
|
|
@ -321,7 +314,7 @@ void Headers::remove_privileged_no_cors_request_headers()
|
|||
// 1. For each headerName of privileged no-CORS request-header names:
|
||||
for (auto const& header_name : privileged_no_cors_request_header_names) {
|
||||
// 1. Delete headerName from headers’s header list.
|
||||
m_header_list->delete_(header_name.bytes());
|
||||
m_header_list->delete_(header_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ private:
|
|||
// A body with type is a tuple that consists of a body (a body) and a type (a header value or null).
|
||||
struct BodyWithType {
|
||||
GC::Ref<Body> body;
|
||||
Optional<ByteBuffer> type;
|
||||
Optional<ByteString> type;
|
||||
};
|
||||
|
||||
WEB_API GC::Ref<Body> byte_sequence_as_body(JS::Realm&, ReadonlyBytes);
|
||||
|
|
|
|||
|
|
@ -10,16 +10,13 @@
|
|||
#include <AK/Checked.h>
|
||||
#include <AK/GenericLexer.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/ScopeGuard.h>
|
||||
#include <AK/StringUtils.h>
|
||||
#include <LibGC/Heap.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibRegex/Regex.h>
|
||||
#include <LibTextCodec/Decoder.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Methods.h>
|
||||
#include <LibWeb/Infra/ByteSequences.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
#include <LibWeb/Loader/ResourceLoader.h>
|
||||
#include <LibWeb/MimeSniff/MimeType.h>
|
||||
|
|
@ -28,71 +25,38 @@ namespace Web::Fetch::Infrastructure {
|
|||
|
||||
GC_DEFINE_ALLOCATOR(HeaderList);
|
||||
|
||||
template<typename T>
|
||||
requires(IsSameIgnoringCV<T, u8>) struct CaseInsensitiveBytesTraits : public Traits<Span<T>> {
|
||||
static constexpr bool equals(Span<T> const& a, Span<T> const& b)
|
||||
{
|
||||
return StringView { a }.equals_ignoring_ascii_case(StringView { b });
|
||||
}
|
||||
|
||||
static constexpr unsigned hash(Span<T> const& span)
|
||||
{
|
||||
if (span.is_empty())
|
||||
return 0;
|
||||
return AK::case_insensitive_string_hash(reinterpret_cast<char const*>(span.data()), span.size());
|
||||
}
|
||||
};
|
||||
|
||||
Header Header::copy(Header const& header)
|
||||
Header Header::isomorphic_encode(StringView name, StringView value)
|
||||
{
|
||||
return Header {
|
||||
.name = MUST(ByteBuffer::copy(header.name)),
|
||||
.value = MUST(ByteBuffer::copy(header.value)),
|
||||
};
|
||||
}
|
||||
Header Header::from_string_pair(StringView name, StringView value)
|
||||
{
|
||||
return Header {
|
||||
return {
|
||||
.name = Infra::isomorphic_encode(name),
|
||||
.value = Infra::isomorphic_encode(value),
|
||||
};
|
||||
}
|
||||
|
||||
Header Header::from_latin1_pair(StringView name, StringView value)
|
||||
{
|
||||
return Header {
|
||||
.name = MUST(ByteBuffer::copy(name.bytes())),
|
||||
.value = MUST(ByteBuffer::copy(value.bytes())),
|
||||
};
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#extract-header-values
|
||||
Optional<Vector<ByteBuffer>> Header::extract_header_values() const
|
||||
Optional<Vector<ByteString>> Header::extract_header_values() const
|
||||
{
|
||||
// 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.
|
||||
|
||||
// For now we only parse some headers that are of the ABNF list form "#something"
|
||||
if (StringView { name }.is_one_of_ignoring_ascii_case(
|
||||
if (name.is_one_of_ignoring_ascii_case(
|
||||
"Access-Control-Request-Headers"sv,
|
||||
"Access-Control-Expose-Headers"sv,
|
||||
"Access-Control-Allow-Headers"sv,
|
||||
"Access-Control-Allow-Methods"sv)
|
||||
&& !value.is_empty()) {
|
||||
auto split_values = StringView { value }.split_view(',');
|
||||
Vector<ByteBuffer> trimmed_values;
|
||||
Vector<ByteString> trimmed_values;
|
||||
|
||||
for (auto const& value : split_values) {
|
||||
auto trimmed_value = value.trim(" \t"sv);
|
||||
auto trimmed_value_as_byte_buffer = MUST(ByteBuffer::copy(trimmed_value.bytes()));
|
||||
trimmed_values.append(move(trimmed_value_as_byte_buffer));
|
||||
}
|
||||
value.view().for_each_split_view(',', SplitBehavior::Nothing, [&](auto value) {
|
||||
trimmed_values.append(value.trim(" \t"sv));
|
||||
});
|
||||
|
||||
return trimmed_values;
|
||||
}
|
||||
|
||||
// This always ignores the ABNF rules for now and returns the header value as a single list item.
|
||||
return Vector { MUST(ByteBuffer::copy(value)) };
|
||||
return Vector { value };
|
||||
}
|
||||
|
||||
GC::Ref<HeaderList> HeaderList::create(JS::VM& vm)
|
||||
|
|
@ -101,16 +65,17 @@ GC::Ref<HeaderList> HeaderList::create(JS::VM& vm)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-list-contains
|
||||
bool HeaderList::contains(ReadonlyBytes name) const
|
||||
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.
|
||||
// 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(*this, [&](auto const& header) {
|
||||
return StringView { header.name }.equals_ignoring_ascii_case(name);
|
||||
return header.name.equals_ignoring_ascii_case(name);
|
||||
});
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-get
|
||||
Optional<ByteBuffer> HeaderList::get(ReadonlyBytes name) const
|
||||
Optional<ByteString> HeaderList::get(StringView name) const
|
||||
{
|
||||
// To get a header name name from a header list list, run these steps:
|
||||
|
||||
|
|
@ -118,25 +83,24 @@ Optional<ByteBuffer> HeaderList::get(ReadonlyBytes name) const
|
|||
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.
|
||||
ByteBuffer buffer;
|
||||
auto first = true;
|
||||
// 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;
|
||||
|
||||
for (auto const& header : *this) {
|
||||
if (!StringView { header.name }.equals_ignoring_ascii_case(name))
|
||||
if (!header.name.equals_ignoring_ascii_case(name))
|
||||
continue;
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
buffer.append(0x2c);
|
||||
buffer.append(0x20);
|
||||
}
|
||||
buffer.append(header.value);
|
||||
|
||||
if (!builder.is_empty())
|
||||
builder.append(", "sv);
|
||||
builder.append(header.value);
|
||||
}
|
||||
return buffer;
|
||||
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-get-decode-split
|
||||
Optional<Vector<String>> HeaderList::get_decode_and_split(ReadonlyBytes name) const
|
||||
Optional<Vector<String>> HeaderList::get_decode_and_split(StringView name) const
|
||||
{
|
||||
// To get, decode, and split a header name name from header list list, run these steps:
|
||||
|
||||
|
|
@ -155,28 +119,28 @@ Optional<Vector<String>> HeaderList::get_decode_and_split(ReadonlyBytes name) co
|
|||
void HeaderList::append(Header header)
|
||||
{
|
||||
// To append a header (name, value) to a header list list, run these steps:
|
||||
// NOTE: Can't use structured bindings captured in the lambda due to https://github.com/llvm/llvm-project/issues/48582
|
||||
auto& name = header.name;
|
||||
|
||||
// 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.
|
||||
if (contains(name)) {
|
||||
auto matching_header = first_matching([&](auto const& existing_header) {
|
||||
return StringView { existing_header.name }.equals_ignoring_ascii_case(name);
|
||||
});
|
||||
name.overwrite(0, matching_header->name.data(), matching_header->name.size());
|
||||
}
|
||||
// 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;
|
||||
|
||||
// 2. Append (name, value) to list.
|
||||
Vector<Header>::append(move(header));
|
||||
Vector::append(move(header));
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-delete
|
||||
void HeaderList::delete_(ReadonlyBytes name)
|
||||
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.
|
||||
// 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.
|
||||
remove_all_matching([&](auto const& header) {
|
||||
return StringView { header.name }.equals_ignoring_ascii_case(name);
|
||||
return header.name.equals_ignoring_ascii_case(name);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -184,23 +148,21 @@ void HeaderList::delete_(ReadonlyBytes name)
|
|||
void HeaderList::set(Header header)
|
||||
{
|
||||
// To set a header (name, value) in a header list list, run these steps:
|
||||
// NOTE: Can't use structured bindings captured in the lambda due to https://github.com/llvm/llvm-project/issues/48582
|
||||
auto const& name = header.name;
|
||||
auto const& value = header.value;
|
||||
|
||||
// 1. If list contains name, then set the value of the first such header to value and remove the others.
|
||||
if (contains(name)) {
|
||||
auto matching_index = find_if([&](auto const& existing_header) {
|
||||
return StringView { existing_header.name }.equals_ignoring_ascii_case(name);
|
||||
}).index();
|
||||
auto& matching_header = at(matching_index);
|
||||
matching_header.value = MUST(ByteBuffer::copy(value));
|
||||
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);
|
||||
|
||||
size_t i = 0;
|
||||
remove_all_matching([&](auto const& existing_header) {
|
||||
ScopeGuard increment_i = [&]() { i++; };
|
||||
if (i <= matching_index)
|
||||
if (i++ <= it.index())
|
||||
return false;
|
||||
return StringView { existing_header.name }.equals_ignoring_ascii_case(name);
|
||||
|
||||
return existing_header.name.equals_ignoring_ascii_case(it->name);
|
||||
});
|
||||
}
|
||||
// 2. Otherwise, append header (name, value) to list.
|
||||
|
|
@ -213,18 +175,15 @@ void HeaderList::set(Header header)
|
|||
void HeaderList::combine(Header header)
|
||||
{
|
||||
// To combine a header (name, value) in a header list list, run these steps:
|
||||
// NOTE: Can't use structured bindings captured in the lambda due to https://github.com/llvm/llvm-project/issues/48582
|
||||
auto const& name = header.name;
|
||||
auto const& value = header.value;
|
||||
|
||||
// 1. If list contains name, then set the value of the first such header to its value, followed by 0x2C 0x20, followed by value.
|
||||
if (contains(name)) {
|
||||
auto matching_header = first_matching([&](auto const& existing_header) {
|
||||
return StringView { existing_header.name }.equals_ignoring_ascii_case(name);
|
||||
});
|
||||
matching_header->value.append(0x2c);
|
||||
matching_header->value.append(0x20);
|
||||
matching_header->value.append(value);
|
||||
// 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);
|
||||
}
|
||||
// 2. Otherwise, append (name, value) to list.
|
||||
else {
|
||||
|
|
@ -240,24 +199,27 @@ 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<ReadonlyBytes> names_list;
|
||||
// 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(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.bytes()) {
|
||||
// 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.
|
||||
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 (StringView { header_name }.equals_ignoring_ascii_case(name)) {
|
||||
if (header_name.equals_ignoring_ascii_case(name)) {
|
||||
// 1. Append (name, value) to headers.
|
||||
auto header = Header::from_string_pair(name, value);
|
||||
headers.append(move(header));
|
||||
headers.append({ name, value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -270,11 +232,7 @@ Vector<Header> HeaderList::sort_and_combine() const
|
|||
VERIFY(value.has_value());
|
||||
|
||||
// 3. Append (name, value) to headers.
|
||||
auto header = Header {
|
||||
.name = move(name),
|
||||
.value = value.release_value(),
|
||||
};
|
||||
headers.append(move(header));
|
||||
headers.empend(move(name), value.release_value());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +241,7 @@ Vector<Header> HeaderList::sort_and_combine() const
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#extract-header-list-values
|
||||
Variant<Empty, Vector<ByteBuffer>, HeaderList::ExtractHeaderParseFailure> HeaderList::extract_header_list_values(ReadonlyBytes name) const
|
||||
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))
|
||||
|
|
@ -293,11 +251,11 @@ Variant<Empty, Vector<ByteBuffer>, HeaderList::ExtractHeaderParseFailure> Header
|
|||
// NOTE: If different error handling is needed, extract the desired header first.
|
||||
|
||||
// 3. Let values be an empty list.
|
||||
auto values = Vector<ByteBuffer> {};
|
||||
Vector<ByteString> values;
|
||||
|
||||
// 4. For each header header list contains whose name is name:
|
||||
for (auto const& header : *this) {
|
||||
if (!StringView { header.name }.equals_ignoring_ascii_case(name))
|
||||
if (!header.name.equals_ignoring_ascii_case(name))
|
||||
continue;
|
||||
|
||||
// 1. Let extract be the result of extracting header values from header.
|
||||
|
|
@ -319,7 +277,7 @@ Variant<Empty, Vector<ByteBuffer>, HeaderList::ExtractHeaderParseFailure> Header
|
|||
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.bytes());
|
||||
auto values = get_decode_and_split("Content-Length"sv);
|
||||
|
||||
// 2. If values is null, then return null.
|
||||
if (!values.has_value())
|
||||
|
|
@ -363,7 +321,7 @@ Optional<MimeSniff::MimeType> HeaderList::extract_mime_type() const
|
|||
Optional<MimeSniff::MimeType> mime_type;
|
||||
|
||||
// 4. Let values be the result of getting, decoding, and splitting `Content-Type` from headers.
|
||||
auto values = get_decode_and_split("Content-Type"sv.bytes());
|
||||
auto values = get_decode_and_split("Content-Type"sv);
|
||||
|
||||
// 5. If values is null, then return failure.
|
||||
if (!values.has_value())
|
||||
|
|
@ -406,61 +364,65 @@ Optional<MimeSniff::MimeType> HeaderList::extract_mime_type() const
|
|||
}
|
||||
|
||||
// Non-standard
|
||||
Vector<ByteBuffer> HeaderList::unique_names() const
|
||||
Vector<ByteString> HeaderList::unique_names() const
|
||||
{
|
||||
Vector<ByteBuffer> header_names_set;
|
||||
HashTable<ReadonlyBytes, CaseInsensitiveBytesTraits<u8 const>> header_names_seen;
|
||||
Vector<ByteString> header_names_set;
|
||||
HashTable<StringView, CaseInsensitiveStringTraits> header_names_seen;
|
||||
|
||||
for (auto const& header : *this) {
|
||||
if (header_names_seen.contains(header.name))
|
||||
continue;
|
||||
|
||||
header_names_set.append(header.name);
|
||||
header_names_seen.set(header.name);
|
||||
header_names_set.append(MUST(ByteBuffer::copy(header.name)));
|
||||
}
|
||||
|
||||
return header_names_set;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-name
|
||||
bool is_header_name(ReadonlyBytes header_name)
|
||||
bool is_header_name(StringView header_name)
|
||||
{
|
||||
// A header name is a byte sequence that matches the field-name token production.
|
||||
Regex<ECMA262Parser> regex { R"~~~(^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$)~~~" };
|
||||
return regex.has_match(StringView { header_name });
|
||||
return regex.has_match(header_name);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-value
|
||||
bool is_header_value(ReadonlyBytes header_value)
|
||||
bool is_header_value(StringView header_value)
|
||||
{
|
||||
// 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;
|
||||
|
||||
auto first_byte = header_value[0];
|
||||
auto last_byte = header_value[header_value.size() - 1];
|
||||
if (HTTP_TAB_OR_SPACE_BYTES.span().contains_slow(first_byte) || HTTP_TAB_OR_SPACE_BYTES.span().contains_slow(last_byte))
|
||||
auto last_byte = header_value[header_value.length() - 1];
|
||||
|
||||
if (is_http_tab_or_space(first_byte) || is_http_tab_or_space(last_byte))
|
||||
return false;
|
||||
|
||||
return !any_of(header_value, [](auto byte) {
|
||||
return byte == 0x00 || HTTP_NEWLINE_BYTES.span().contains_slow(byte);
|
||||
return byte == 0x00 || is_http_newline(byte);
|
||||
});
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-value-normalize
|
||||
ByteBuffer normalize_header_value(ReadonlyBytes potential_value)
|
||||
ByteString normalize_header_value(StringView potential_value)
|
||||
{
|
||||
// To normalize a byte sequence potentialValue, remove any leading and trailing HTTP whitespace bytes from potentialValue.
|
||||
// To normalize a byte sequence potentialValue, remove any leading and trailing HTTP whitespace bytes from
|
||||
// potentialValue.
|
||||
if (potential_value.is_empty())
|
||||
return {};
|
||||
auto trimmed = StringView { potential_value }.trim(HTTP_WHITESPACE, TrimMode::Both);
|
||||
return MUST(ByteBuffer::copy(trimmed.bytes()));
|
||||
return potential_value.trim(HTTP_WHITESPACE, TrimMode::Both);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#forbidden-header-name
|
||||
bool is_forbidden_request_header(Header const& header)
|
||||
{
|
||||
// A header (name, value) is forbidden request-header if these steps return true:
|
||||
auto name = StringView { header.name };
|
||||
auto const& [name, value] = header;
|
||||
|
||||
// 1. If name is a byte-case-insensitive match for one of:
|
||||
// [...]
|
||||
|
|
@ -506,11 +468,11 @@ bool is_forbidden_request_header(Header const& header)
|
|||
"X-HTTP-Method-Override"sv,
|
||||
"X-Method-Override"sv)) {
|
||||
// 1. Let parsedValues be the result of getting, decoding, and splitting value.
|
||||
auto parsed_values = get_decode_and_split_header_value(header.value);
|
||||
auto parsed_values = get_decode_and_split_header_value(value);
|
||||
|
||||
// 2. For each method of parsedValues: if the isomorphic encoding of method is a forbidden method, then return true.
|
||||
// Note: The values returned from get_decode_and_split_header_value have already been decoded.
|
||||
if (parsed_values.has_value() && any_of(*parsed_values, [](auto method) { return is_forbidden_method(method.bytes()); }))
|
||||
// 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); }))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -519,12 +481,12 @@ bool is_forbidden_request_header(Header const& header)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#forbidden-response-header-name
|
||||
bool is_forbidden_response_header_name(ReadonlyBytes header_name)
|
||||
bool is_forbidden_response_header_name(StringView header_name)
|
||||
{
|
||||
// A forbidden response-header name is a header name that is a byte-case-insensitive match for one of:
|
||||
// - `Set-Cookie`
|
||||
// - `Set-Cookie2`
|
||||
return StringView { header_name }.is_one_of_ignoring_ascii_case(
|
||||
return header_name.is_one_of_ignoring_ascii_case(
|
||||
"Set-Cookie"sv,
|
||||
"Set-Cookie2"sv);
|
||||
}
|
||||
|
|
@ -553,7 +515,7 @@ StringView legacy_extract_an_encoding(Optional<MimeSniff::MimeType> const& mime_
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#header-value-get-decode-and-split
|
||||
Optional<Vector<String>> get_decode_and_split_header_value(ReadonlyBytes value)
|
||||
Vector<String> get_decode_and_split_header_value(StringView value)
|
||||
{
|
||||
// To get, decode, and split a header value value, run these steps:
|
||||
|
||||
|
|
@ -571,7 +533,8 @@ Optional<Vector<String>> get_decode_and_split_header_value(ReadonlyBytes value)
|
|||
|
||||
// 5. While true:
|
||||
while (true) {
|
||||
// 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.
|
||||
// 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.
|
||||
// NOTE: The result might be the empty string.
|
||||
temporary_value_builder.append(lexer.consume_until(is_any_of("\","sv)));
|
||||
|
||||
|
|
@ -607,34 +570,26 @@ Optional<Vector<String>> get_decode_and_split_header_value(ReadonlyBytes value)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#convert-header-names-to-a-sorted-lowercase-set
|
||||
OrderedHashTable<ByteBuffer> convert_header_names_to_a_sorted_lowercase_set(Span<ReadonlyBytes> header_names)
|
||||
Vector<ByteString> convert_header_names_to_a_sorted_lowercase_set(ReadonlySpan<ByteString> header_names)
|
||||
{
|
||||
// To convert header names to a sorted-lowercase set, given a list of names headerNames, run these steps:
|
||||
|
||||
// 1. Let headerNamesSet be a new ordered set.
|
||||
Vector<ByteBuffer> header_names_set;
|
||||
HashTable<ReadonlyBytes, CaseInsensitiveBytesTraits<u8 const>> header_names_seen;
|
||||
HashTable<StringView, CaseInsensitiveStringTraits> header_names_seen;
|
||||
Vector<ByteString> header_names_set;
|
||||
|
||||
// 2. For each name of headerNames, append the result of byte-lowercasing name to headerNamesSet.
|
||||
for (auto name : header_names) {
|
||||
for (auto const& name : header_names) {
|
||||
if (header_names_seen.contains(name))
|
||||
continue;
|
||||
auto bytes = MUST(ByteBuffer::copy(name));
|
||||
Infra::byte_lowercase(bytes);
|
||||
|
||||
header_names_seen.set(name);
|
||||
header_names_set.append(move(bytes));
|
||||
header_names_set.append(name.to_lowercase());
|
||||
}
|
||||
|
||||
// 3. Return the result of sorting headerNamesSet in ascending order with byte less than.
|
||||
quick_sort(header_names_set, [](auto const& a, auto const& b) {
|
||||
return StringView { a } < StringView { b };
|
||||
});
|
||||
OrderedHashTable<ByteBuffer> ordered { header_names_set.size() };
|
||||
for (auto& name : header_names_set) {
|
||||
auto result = ordered.set(move(name));
|
||||
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
||||
}
|
||||
return ordered;
|
||||
quick_sort(header_names_set);
|
||||
return header_names_set;
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#build-a-content-range
|
||||
|
|
@ -651,7 +606,7 @@ ByteString build_content_range(u64 range_start, u64 range_end, u64 full_length)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#simple-range-header-value
|
||||
Optional<RangeHeaderValue> parse_single_range_header_value(ReadonlyBytes const value, bool const allow_whitespace)
|
||||
Optional<RangeHeaderValue> parse_single_range_header_value(StringView const value, bool const allow_whitespace)
|
||||
{
|
||||
// 1. Let data be the isomorphic decoding of value.
|
||||
auto const data = Infra::isomorphic_decode(value);
|
||||
|
|
@ -722,35 +677,32 @@ Optional<RangeHeaderValue> parse_single_range_header_value(ReadonlyBytes const v
|
|||
bool is_cors_safelisted_request_header(Header const& header)
|
||||
{
|
||||
// To determine whether a header (name, value) is a CORS-safelisted request-header, run these steps:
|
||||
|
||||
auto const& value = header.value;
|
||||
auto const& [name, value] = header;
|
||||
|
||||
// 1. If value’s length is greater than 128, then return false.
|
||||
if (value.size() > 128)
|
||||
if (value.length() > 128)
|
||||
return false;
|
||||
|
||||
// 2. Byte-lowercase name and switch on the result:
|
||||
auto name = StringView { header.name };
|
||||
|
||||
// `accept`
|
||||
if (name.equals_ignoring_ascii_case("accept"sv)) {
|
||||
// If value contains a CORS-unsafe request-header byte, then return false.
|
||||
if (any_of(value.span(), is_cors_unsafe_request_header_byte))
|
||||
if (any_of(value, is_cors_unsafe_request_header_byte))
|
||||
return false;
|
||||
}
|
||||
// `accept-language`
|
||||
// `content-language`
|
||||
else if (name.is_one_of_ignoring_ascii_case("accept-language"sv, "content-language"sv)) {
|
||||
// If value contains a byte that is not in the range 0x30 (0) to 0x39 (9), inclusive, is not in the range 0x41 (A) to 0x5A (Z), inclusive, is not in the range 0x61 (a) to 0x7A (z), inclusive, and is not 0x20 (SP), 0x2A (*), 0x2C (,), 0x2D (-), 0x2E (.), 0x3B (;), or 0x3D (=), then return false.
|
||||
if (any_of(value.span(), [](auto byte) {
|
||||
return !(is_ascii_digit(byte) || is_ascii_alpha(byte) || " *,-.;="sv.contains(static_cast<char>(byte)));
|
||||
if (any_of(value, [](auto byte) {
|
||||
return !(is_ascii_digit(byte) || is_ascii_alpha(byte) || " *,-.;="sv.contains(byte));
|
||||
}))
|
||||
return false;
|
||||
}
|
||||
// `content-type`
|
||||
else if (name.equals_ignoring_ascii_case("content-type"sv)) {
|
||||
// 1. If value contains a CORS-unsafe request-header byte, then return false.
|
||||
if (any_of(value.span(), is_cors_unsafe_request_header_byte))
|
||||
if (any_of(value, is_cors_unsafe_request_header_byte))
|
||||
return false;
|
||||
|
||||
// 2. Let mimeType be the result of parsing the result of isomorphic decoding value.
|
||||
|
|
@ -800,15 +752,15 @@ bool is_cors_unsafe_request_header_byte(u8 byte)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names
|
||||
OrderedHashTable<ByteBuffer> get_cors_unsafe_header_names(HeaderList const& headers)
|
||||
Vector<ByteString> get_cors_unsafe_header_names(HeaderList const& headers)
|
||||
{
|
||||
// The CORS-unsafe request-header names, given a header list headers, are determined as follows:
|
||||
|
||||
// 1. Let unsafeNames be a new list.
|
||||
Vector<ReadonlyBytes> unsafe_names;
|
||||
Vector<ByteString> unsafe_names;
|
||||
|
||||
// 2. Let potentiallyUnsafeNames be a new list.
|
||||
Vector<ReadonlyBytes> potentially_unsafe_names;
|
||||
Vector<ByteString> potentially_unsafe_names;
|
||||
|
||||
// 3. Let safelistValueSize be 0.
|
||||
Checked<size_t> safelist_value_size = 0;
|
||||
|
|
@ -817,42 +769,42 @@ OrderedHashTable<ByteBuffer> get_cors_unsafe_header_names(HeaderList const& head
|
|||
for (auto const& header : headers) {
|
||||
// 1. If header is not a CORS-safelisted request-header, then append header’s name to unsafeNames.
|
||||
if (!is_cors_safelisted_request_header(header)) {
|
||||
unsafe_names.append(header.name.span());
|
||||
unsafe_names.append(header.name);
|
||||
}
|
||||
// 2. Otherwise, append header’s name to potentiallyUnsafeNames and increase safelistValueSize by header’s value’s length.
|
||||
// 2. Otherwise, append header’s name to potentiallyUnsafeNames and increase safelistValueSize by header’s
|
||||
// value’s length.
|
||||
else {
|
||||
potentially_unsafe_names.append(header.name.span());
|
||||
safelist_value_size += header.value.size();
|
||||
potentially_unsafe_names.append(header.name);
|
||||
safelist_value_size += header.value.length();
|
||||
}
|
||||
}
|
||||
|
||||
// 5. If safelistValueSize is greater than 1024, then for each name of potentiallyUnsafeNames, append name to unsafeNames.
|
||||
if (safelist_value_size.has_overflow() || safelist_value_size.value() > 1024) {
|
||||
for (auto const& name : potentially_unsafe_names)
|
||||
unsafe_names.append(name);
|
||||
}
|
||||
// 5. If safelistValueSize is greater than 1024, then for each name of potentiallyUnsafeNames, append name to
|
||||
// unsafeNames.
|
||||
if (safelist_value_size.has_overflow() || safelist_value_size.value() > 1024)
|
||||
unsafe_names.extend(move(potentially_unsafe_names));
|
||||
|
||||
// 6. Return the result of convert header names to a sorted-lowercase set with unsafeNames.
|
||||
return convert_header_names_to_a_sorted_lowercase_set(unsafe_names.span());
|
||||
return convert_header_names_to_a_sorted_lowercase_set(unsafe_names);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name
|
||||
bool is_cors_non_wildcard_request_header_name(ReadonlyBytes header_name)
|
||||
bool is_cors_non_wildcard_request_header_name(StringView header_name)
|
||||
{
|
||||
// A CORS non-wildcard request-header name is a header name that is a byte-case-insensitive match for `Authorization`.
|
||||
return StringView { header_name }.equals_ignoring_ascii_case("Authorization"sv);
|
||||
return header_name.equals_ignoring_ascii_case("Authorization"sv);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name
|
||||
bool is_privileged_no_cors_request_header_name(ReadonlyBytes header_name)
|
||||
bool is_privileged_no_cors_request_header_name(StringView header_name)
|
||||
{
|
||||
// A privileged no-CORS request-header name is a header name that is a byte-case-insensitive match for one of
|
||||
// - `Range`.
|
||||
return StringView { header_name }.equals_ignoring_ascii_case("Range"sv);
|
||||
return header_name.equals_ignoring_ascii_case("Range"sv);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name
|
||||
bool is_cors_safelisted_response_header_name(ReadonlyBytes header_name, Span<ReadonlyBytes> list)
|
||||
bool is_cors_safelisted_response_header_name(StringView header_name, ReadonlySpan<StringView> list)
|
||||
{
|
||||
// A CORS-safelisted response-header name, given a list of header names list, is a header name that is a byte-case-insensitive match for one of
|
||||
// - `Cache-Control`
|
||||
|
|
@ -863,7 +815,7 @@ bool is_cors_safelisted_response_header_name(ReadonlyBytes header_name, Span<Rea
|
|||
// - `Last-Modified`
|
||||
// - `Pragma`
|
||||
// - Any item in list that is not a forbidden response-header name.
|
||||
return StringView { header_name }.is_one_of_ignoring_ascii_case(
|
||||
return header_name.is_one_of_ignoring_ascii_case(
|
||||
"Cache-Control"sv,
|
||||
"Content-Language"sv,
|
||||
"Content-Length"sv,
|
||||
|
|
@ -872,20 +824,20 @@ bool is_cors_safelisted_response_header_name(ReadonlyBytes header_name, Span<Rea
|
|||
"Last-Modified"sv,
|
||||
"Pragma"sv)
|
||||
|| any_of(list, [&](auto list_header_name) {
|
||||
return StringView { header_name }.equals_ignoring_ascii_case(list_header_name)
|
||||
return header_name.equals_ignoring_ascii_case(list_header_name)
|
||||
&& !is_forbidden_response_header_name(list_header_name);
|
||||
});
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#no-cors-safelisted-request-header-name
|
||||
bool is_no_cors_safelisted_request_header_name(ReadonlyBytes header_name)
|
||||
bool is_no_cors_safelisted_request_header_name(StringView header_name)
|
||||
{
|
||||
// A no-CORS-safelisted request-header name is a header name that is a byte-case-insensitive match for one of
|
||||
// - `Accept`
|
||||
// - `Accept-Language`
|
||||
// - `Content-Language`
|
||||
// - `Content-Type`
|
||||
return StringView { header_name }.is_one_of_ignoring_ascii_case(
|
||||
return header_name.is_one_of_ignoring_ascii_case(
|
||||
"Accept"sv,
|
||||
"Accept-Language"sv,
|
||||
"Content-Language"sv,
|
||||
|
|
@ -906,10 +858,11 @@ bool is_no_cors_safelisted_request_header(Header const& header)
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#default-user-agent-value
|
||||
ByteBuffer default_user_agent_value()
|
||||
ByteString const& default_user_agent_value()
|
||||
{
|
||||
// A default `User-Agent` value is an implementation-defined header value for the `User-Agent` header.
|
||||
return MUST(ByteBuffer::copy(ResourceLoader::the().user_agent().bytes()));
|
||||
static auto user_agent = ResourceLoader::the().user_agent().to_byte_string();
|
||||
return user_agent;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
|
|
@ -25,14 +22,12 @@ namespace Web::Fetch::Infrastructure {
|
|||
// https://fetch.spec.whatwg.org/#concept-header
|
||||
// A header is a tuple that consists of a name (a header name) and value (a header value).
|
||||
struct WEB_API Header {
|
||||
[[nodiscard]] static Header copy(Header const&);
|
||||
[[nodiscard]] static Header from_string_pair(StringView, StringView);
|
||||
[[nodiscard]] static Header from_latin1_pair(StringView, StringView);
|
||||
[[nodiscard]] static Header isomorphic_encode(StringView, StringView);
|
||||
|
||||
[[nodiscard]] Optional<Vector<ByteBuffer>> extract_header_values() const;
|
||||
Optional<Vector<ByteString>> extract_header_values() const;
|
||||
|
||||
ByteBuffer name;
|
||||
ByteBuffer value;
|
||||
ByteString name;
|
||||
ByteString value;
|
||||
};
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list
|
||||
|
|
@ -51,24 +46,24 @@ public:
|
|||
using Vector::end;
|
||||
using Vector::is_empty;
|
||||
|
||||
[[nodiscard]] bool contains(ReadonlyBytes) const;
|
||||
[[nodiscard]] Optional<ByteBuffer> get(ReadonlyBytes) const;
|
||||
[[nodiscard]] Optional<Vector<String>> get_decode_and_split(ReadonlyBytes) const;
|
||||
[[nodiscard]] bool contains(StringView) const;
|
||||
[[nodiscard]] Optional<ByteString> get(StringView) const;
|
||||
[[nodiscard]] Optional<Vector<String>> get_decode_and_split(StringView) const;
|
||||
void append(Header);
|
||||
void delete_(ReadonlyBytes name);
|
||||
void delete_(StringView name);
|
||||
void set(Header);
|
||||
void combine(Header);
|
||||
[[nodiscard]] Vector<Header> sort_and_combine() const;
|
||||
|
||||
struct ExtractHeaderParseFailure { };
|
||||
[[nodiscard]] Variant<Empty, Vector<ByteBuffer>, ExtractHeaderParseFailure> extract_header_list_values(ReadonlyBytes) const;
|
||||
[[nodiscard]] Variant<Empty, Vector<ByteString>, ExtractHeaderParseFailure> extract_header_list_values(StringView) const;
|
||||
|
||||
struct ExtractLengthFailure { };
|
||||
[[nodiscard]] Variant<Empty, u64, ExtractLengthFailure> extract_length() const;
|
||||
|
||||
[[nodiscard]] Optional<MimeSniff::MimeType> extract_mime_type() const;
|
||||
|
||||
[[nodiscard]] Vector<ByteBuffer> unique_names() const;
|
||||
[[nodiscard]] Vector<ByteString> unique_names() const;
|
||||
};
|
||||
|
||||
struct RangeHeaderValue {
|
||||
|
|
@ -76,29 +71,29 @@ struct RangeHeaderValue {
|
|||
Optional<u64> end;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool is_header_name(ReadonlyBytes);
|
||||
[[nodiscard]] bool is_header_value(ReadonlyBytes);
|
||||
[[nodiscard]] ByteBuffer normalize_header_value(ReadonlyBytes);
|
||||
[[nodiscard]] bool is_header_name(StringView);
|
||||
[[nodiscard]] bool is_header_value(StringView);
|
||||
[[nodiscard]] ByteString normalize_header_value(StringView);
|
||||
|
||||
[[nodiscard]] bool is_forbidden_request_header(Header const&);
|
||||
[[nodiscard]] bool is_forbidden_response_header_name(ReadonlyBytes);
|
||||
[[nodiscard]] bool is_forbidden_response_header_name(StringView);
|
||||
|
||||
[[nodiscard]] WEB_API StringView legacy_extract_an_encoding(Optional<MimeSniff::MimeType> const& mime_type, StringView fallback_encoding);
|
||||
[[nodiscard]] Optional<Vector<String>> get_decode_and_split_header_value(ReadonlyBytes);
|
||||
[[nodiscard]] OrderedHashTable<ByteBuffer> convert_header_names_to_a_sorted_lowercase_set(Span<ReadonlyBytes>);
|
||||
[[nodiscard]] Vector<String> get_decode_and_split_header_value(StringView);
|
||||
[[nodiscard]] Vector<ByteString> convert_header_names_to_a_sorted_lowercase_set(ReadonlySpan<ByteString>);
|
||||
|
||||
[[nodiscard]] WEB_API ByteString build_content_range(u64 range_start, u64 range_end, u64 full_length);
|
||||
[[nodiscard]] WEB_API Optional<RangeHeaderValue> parse_single_range_header_value(ReadonlyBytes, bool);
|
||||
[[nodiscard]] WEB_API Optional<RangeHeaderValue> parse_single_range_header_value(StringView, bool);
|
||||
|
||||
[[nodiscard]] bool is_cors_safelisted_request_header(Header const&);
|
||||
[[nodiscard]] bool is_cors_unsafe_request_header_byte(u8);
|
||||
[[nodiscard]] WEB_API OrderedHashTable<ByteBuffer> get_cors_unsafe_header_names(HeaderList const&);
|
||||
[[nodiscard]] WEB_API bool is_cors_non_wildcard_request_header_name(ReadonlyBytes);
|
||||
[[nodiscard]] bool is_privileged_no_cors_request_header_name(ReadonlyBytes);
|
||||
[[nodiscard]] bool is_cors_safelisted_response_header_name(ReadonlyBytes, Span<ReadonlyBytes>);
|
||||
[[nodiscard]] bool is_no_cors_safelisted_request_header_name(ReadonlyBytes);
|
||||
[[nodiscard]] WEB_API Vector<ByteString> get_cors_unsafe_header_names(HeaderList const&);
|
||||
[[nodiscard]] WEB_API bool is_cors_non_wildcard_request_header_name(StringView);
|
||||
[[nodiscard]] bool is_privileged_no_cors_request_header_name(StringView);
|
||||
[[nodiscard]] bool is_cors_safelisted_response_header_name(StringView, ReadonlySpan<StringView>);
|
||||
[[nodiscard]] bool is_no_cors_safelisted_request_header_name(StringView);
|
||||
[[nodiscard]] bool is_no_cors_safelisted_request_header(Header const&);
|
||||
|
||||
[[nodiscard]] WEB_API ByteBuffer default_user_agent_value();
|
||||
[[nodiscard]] WEB_API ByteString const& default_user_agent_value();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,44 +5,46 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibRegex/Regex.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Methods.h>
|
||||
#include <LibWeb/Infra/ByteSequences.h>
|
||||
|
||||
namespace Web::Fetch::Infrastructure {
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-method
|
||||
bool is_method(ReadonlyBytes method)
|
||||
bool is_method(StringView method)
|
||||
{
|
||||
// A method is a byte sequence that matches the method token production.
|
||||
Regex<ECMA262Parser> regex { R"~~~(^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$)~~~" };
|
||||
return regex.has_match(StringView { method });
|
||||
return regex.has_match(method);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#cors-safelisted-method
|
||||
bool is_cors_safelisted_method(ReadonlyBytes method)
|
||||
bool is_cors_safelisted_method(StringView method)
|
||||
{
|
||||
// A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`.
|
||||
return StringView { method }.is_one_of("GET"sv, "HEAD"sv, "POST"sv);
|
||||
return method.is_one_of("GET"sv, "HEAD"sv, "POST"sv);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#forbidden-method
|
||||
bool is_forbidden_method(ReadonlyBytes method)
|
||||
bool is_forbidden_method(StringView method)
|
||||
{
|
||||
// A forbidden method is a method that is a byte-case-insensitive match for `CONNECT`, `TRACE`, or `TRACK`.
|
||||
return StringView { method }.is_one_of_ignoring_ascii_case("CONNECT"sv, "TRACE"sv, "TRACK"sv);
|
||||
return method.is_one_of_ignoring_ascii_case("CONNECT"sv, "TRACE"sv, "TRACK"sv);
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-method-normalize
|
||||
ByteBuffer normalize_method(ReadonlyBytes method)
|
||||
ByteString normalize_method(StringView method)
|
||||
{
|
||||
// To normalize a method, if it is a byte-case-insensitive match for `DELETE`, `GET`, `HEAD`, `OPTIONS`, `POST`, or `PUT`, byte-uppercase it.
|
||||
auto bytes = MUST(ByteBuffer::copy(method));
|
||||
if (StringView { method }.is_one_of_ignoring_ascii_case("DELETE"sv, "GET"sv, "HEAD"sv, "OPTIONS"sv, "POST"sv, "PUT"sv))
|
||||
Infra::byte_uppercase(bytes);
|
||||
return bytes;
|
||||
// To normalize a method, if it is a byte-case-insensitive match for `DELETE`, `GET`, `HEAD`, `OPTIONS`, `POST`,
|
||||
// or `PUT`, byte-uppercase it.
|
||||
static auto NORMALIZED_METHODS = to_array<ByteString>({ "DELETE"sv, "GET"sv, "HEAD"sv, "OPTIONS"sv, "POST"sv, "PUT"sv });
|
||||
|
||||
for (auto const& normalized_method : NORMALIZED_METHODS) {
|
||||
if (normalized_method.equals_ignoring_ascii_case(method))
|
||||
return normalized_method;
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,14 +6,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibWeb/Export.h>
|
||||
|
||||
namespace Web::Fetch::Infrastructure {
|
||||
|
||||
[[nodiscard]] bool is_method(ReadonlyBytes);
|
||||
[[nodiscard]] WEB_API bool is_cors_safelisted_method(ReadonlyBytes);
|
||||
[[nodiscard]] bool is_forbidden_method(ReadonlyBytes);
|
||||
[[nodiscard]] ByteBuffer normalize_method(ReadonlyBytes);
|
||||
[[nodiscard]] bool is_method(StringView);
|
||||
[[nodiscard]] WEB_API bool is_cors_safelisted_method(StringView);
|
||||
[[nodiscard]] bool is_forbidden_method(StringView);
|
||||
[[nodiscard]] ByteString normalize_method(StringView);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <LibWeb/Fetch/Fetching/PendingResponse.h>
|
||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
|
||||
#include <LibWeb/HTML/TraversableNavigable.h>
|
||||
#include <LibWeb/Infra/Strings.h>
|
||||
|
||||
namespace Web::Fetch::Infrastructure {
|
||||
|
||||
|
|
@ -212,11 +213,11 @@ String Request::serialize_origin() const
|
|||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#byte-serializing-a-request-origin
|
||||
ByteBuffer Request::byte_serialize_origin() const
|
||||
ByteString Request::byte_serialize_origin() const
|
||||
{
|
||||
// Byte-serializing a request origin, given a request request, is to return the result of serializing a request origin with request, isomorphic encoded.
|
||||
auto serialized_origin = serialize_origin();
|
||||
return MUST(ByteBuffer::copy(serialized_origin.bytes()));
|
||||
// Byte-serializing a request origin, given a request request, is to return the result of serializing a request
|
||||
// origin with request, isomorphic encoded.
|
||||
return Infra::isomorphic_encode(serialize_origin());
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-request-clone
|
||||
|
|
@ -283,27 +284,15 @@ void Request::add_range_header(u64 first, Optional<u64> const& last)
|
|||
VERIFY(!last.has_value() || first <= last.value());
|
||||
|
||||
// 2. Let rangeValue be `bytes=`.
|
||||
auto range_value = MUST(ByteBuffer::copy("bytes"sv.bytes()));
|
||||
|
||||
// 3. Serialize and isomorphic encode first, and append the result to rangeValue.
|
||||
auto serialized_first = String::number(first);
|
||||
range_value.append(serialized_first.bytes());
|
||||
|
||||
// 4. Append 0x2D (-) to rangeValue.
|
||||
range_value.append('-');
|
||||
|
||||
// 5. If last is given, then serialize and isomorphic encode it, and append the result to rangeValue.
|
||||
if (last.has_value()) {
|
||||
auto serialized_last = String::number(*last);
|
||||
range_value.append(serialized_last.bytes());
|
||||
}
|
||||
auto range_value = last.has_value()
|
||||
? ByteString::formatted("bytes={}-{}", first, *last)
|
||||
: ByteString::formatted("bytes={}-", first);
|
||||
|
||||
// 6. Append (`Range`, rangeValue) to request’s header list.
|
||||
auto header = Header {
|
||||
.name = MUST(ByteBuffer::copy("Range"sv.bytes())),
|
||||
.value = move(range_value),
|
||||
};
|
||||
m_header_list->append(move(header));
|
||||
m_header_list->append({ "Range"sv, move(range_value) });
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#append-a-request-origin-header
|
||||
|
|
@ -314,21 +303,17 @@ void Request::add_origin_header()
|
|||
|
||||
// 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list.
|
||||
if (m_response_tainting == ResponseTainting::CORS || m_mode == Mode::WebSocket) {
|
||||
auto header = Header {
|
||||
.name = MUST(ByteBuffer::copy("Origin"sv.bytes())),
|
||||
.value = move(serialized_origin),
|
||||
};
|
||||
m_header_list->append(move(header));
|
||||
m_header_list->append({ "Origin"sv, move(serialized_origin) });
|
||||
}
|
||||
// 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
|
||||
else if (!StringView { m_method }.is_one_of("GET"sv, "HEAD"sv)) {
|
||||
else if (!m_method.is_one_of("GET"sv, "HEAD"sv)) {
|
||||
// 1. If request’s mode is not "cors", then switch on request’s referrer policy:
|
||||
if (m_mode != Mode::CORS) {
|
||||
switch (m_referrer_policy) {
|
||||
// -> "no-referrer"
|
||||
case ReferrerPolicy::ReferrerPolicy::NoReferrer:
|
||||
// Set serializedOrigin to `null`.
|
||||
serialized_origin = MUST(ByteBuffer::copy("null"sv.bytes()));
|
||||
serialized_origin = "null"sv;
|
||||
break;
|
||||
// -> "no-referrer-when-downgrade"
|
||||
// -> "strict-origin"
|
||||
|
|
@ -339,14 +324,14 @@ void Request::add_origin_header()
|
|||
// If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is
|
||||
// not "https", then set serializedOrigin to `null`.
|
||||
if (m_origin.has<URL::Origin>() && !m_origin.get<URL::Origin>().is_opaque() && m_origin.get<URL::Origin>().scheme() == "https"sv && current_url().scheme() != "https"sv)
|
||||
serialized_origin = MUST(ByteBuffer::copy("null"sv.bytes()));
|
||||
serialized_origin = "null"sv;
|
||||
break;
|
||||
// -> "same-origin"
|
||||
case ReferrerPolicy::ReferrerPolicy::SameOrigin:
|
||||
// If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin
|
||||
// to `null`.
|
||||
if (m_origin.has<URL::Origin>() && !m_origin.get<URL::Origin>().is_same_origin(current_url().origin()))
|
||||
serialized_origin = MUST(ByteBuffer::copy("null"sv.bytes()));
|
||||
serialized_origin = "null"sv;
|
||||
break;
|
||||
// -> Otherwise
|
||||
default:
|
||||
|
|
@ -356,11 +341,7 @@ void Request::add_origin_header()
|
|||
}
|
||||
|
||||
// 2. Append (`Origin`, serializedOrigin) to request’s header list.
|
||||
auto header = Header {
|
||||
.name = MUST(ByteBuffer::copy("Origin"sv.bytes())),
|
||||
.value = move(serialized_origin),
|
||||
};
|
||||
m_header_list->append(move(header));
|
||||
m_header_list->append({ "Origin"sv, move(serialized_origin) });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Optional.h>
|
||||
|
|
@ -171,8 +172,8 @@ public:
|
|||
|
||||
[[nodiscard]] static GC::Ref<Request> create(JS::VM&);
|
||||
|
||||
[[nodiscard]] ReadonlyBytes method() const LIFETIME_BOUND { return m_method; }
|
||||
void set_method(ByteBuffer method) { m_method = move(method); }
|
||||
[[nodiscard]] ByteString const& method() const { return m_method; }
|
||||
void set_method(ByteString method) { m_method = move(method); }
|
||||
|
||||
[[nodiscard]] bool local_urls_only() const { return m_local_urls_only; }
|
||||
void set_local_urls_only(bool local_urls_only) { m_local_urls_only = local_urls_only; }
|
||||
|
|
@ -307,7 +308,7 @@ public:
|
|||
[[nodiscard]] RedirectTaint redirect_taint() const;
|
||||
|
||||
[[nodiscard]] String serialize_origin() const;
|
||||
[[nodiscard]] ByteBuffer byte_serialize_origin() const;
|
||||
[[nodiscard]] ByteString byte_serialize_origin() const;
|
||||
|
||||
[[nodiscard]] GC::Ref<Request> clone(JS::Realm&) const;
|
||||
|
||||
|
|
@ -335,7 +336,7 @@ private:
|
|||
|
||||
// https://fetch.spec.whatwg.org/#concept-request-method
|
||||
// A request has an associated method (a method). Unless stated otherwise it is `GET`.
|
||||
ByteBuffer m_method { ByteBuffer::copy("GET"sv.bytes()).release_value() };
|
||||
ByteString m_method { "GET"sv };
|
||||
|
||||
// https://fetch.spec.whatwg.org/#local-urls-only-flag
|
||||
// A request has an associated local-URLs-only flag. Unless stated otherwise it is unset.
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ ErrorOr<Optional<URL::URL>> Response::location_url(Optional<String> const& reque
|
|||
return Optional<URL::URL> {};
|
||||
|
||||
// 2. Let location be the result of extracting header list values given `Location` and response’s header list.
|
||||
auto location_values_or_failure = m_header_list->extract_header_list_values("Location"sv.bytes());
|
||||
auto const* location_values = location_values_or_failure.get_pointer<Vector<ByteBuffer>>();
|
||||
auto location_values_or_failure = m_header_list->extract_header_list_values("Location"sv);
|
||||
auto const* location_values = location_values_or_failure.get_pointer<Vector<ByteString>>();
|
||||
|
||||
if (!location_values || location_values->size() != 1)
|
||||
return OptionalNone {};
|
||||
|
|
@ -246,8 +246,8 @@ u64 Response::current_age() const
|
|||
{
|
||||
// The term "age_value" denotes the value of the Age header field (Section 5.1), in a form appropriate for arithmetic operation; or 0, if not available.
|
||||
Optional<AK::Duration> age;
|
||||
if (auto const age_header = header_list()->get("Age"sv.bytes()); age_header.has_value()) {
|
||||
if (auto converted_age = StringView { *age_header }.to_number<u64>(); converted_age.has_value())
|
||||
if (auto const age_header = header_list()->get("Age"sv); age_header.has_value()) {
|
||||
if (auto converted_age = age_header->to_number<u64>(); converted_age.has_value())
|
||||
age = AK::Duration::from_seconds(converted_age.value());
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +281,7 @@ u64 Response::current_age() const
|
|||
// https://httpwg.org/specs/rfc9111.html#calculating.freshness.lifetime
|
||||
u64 Response::freshness_lifetime() const
|
||||
{
|
||||
auto const elem = header_list()->get_decode_and_split("Cache-Control"sv.bytes());
|
||||
auto const elem = header_list()->get_decode_and_split("Cache-Control"sv);
|
||||
if (!elem.has_value())
|
||||
return 0;
|
||||
|
||||
|
|
@ -314,7 +314,7 @@ u64 Response::freshness_lifetime() const
|
|||
// https://httpwg.org/specs/rfc5861.html#n-the-stale-while-revalidate-cache-control-extension
|
||||
u64 Response::stale_while_revalidate_lifetime() const
|
||||
{
|
||||
auto const elem = header_list()->get_decode_and_split("Cache-Control"sv.bytes());
|
||||
auto const elem = header_list()->get_decode_and_split("Cache-Control"sv);
|
||||
if (!elem.has_value())
|
||||
return 0;
|
||||
|
||||
|
|
@ -386,9 +386,11 @@ GC::Ref<CORSFilteredResponse> CORSFilteredResponse::create(JS::VM& vm, GC::Ref<R
|
|||
// A CORS filtered response is a filtered response whose type is "cors" and header list excludes
|
||||
// any headers in internal response’s header list whose name is not a CORS-safelisted response-header
|
||||
// name, given internal response’s CORS-exposed header-name list.
|
||||
Vector<ReadonlyBytes> cors_exposed_header_name_list;
|
||||
Vector<StringView> cors_exposed_header_name_list;
|
||||
cors_exposed_header_name_list.ensure_capacity(internal_response->cors_exposed_header_name_list().size());
|
||||
|
||||
for (auto const& header_name : internal_response->cors_exposed_header_name_list())
|
||||
cors_exposed_header_name_list.append(header_name.span());
|
||||
cors_exposed_header_name_list.unchecked_append(header_name);
|
||||
|
||||
auto header_list = HeaderList::create(vm);
|
||||
for (auto const& header : *internal_response->header_list()) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Error.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/Optional.h>
|
||||
|
|
@ -78,8 +78,8 @@ public:
|
|||
[[nodiscard]] virtual Status status() const { return m_status; }
|
||||
virtual void set_status(Status status) { m_status = status; }
|
||||
|
||||
[[nodiscard]] virtual ReadonlyBytes status_message() const LIFETIME_BOUND { return m_status_message; }
|
||||
virtual void set_status_message(ByteBuffer status_message) { m_status_message = move(status_message); }
|
||||
[[nodiscard]] virtual ByteString const& status_message() const { return m_status_message; }
|
||||
virtual void set_status_message(ByteString status_message) { m_status_message = move(status_message); }
|
||||
|
||||
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const { return m_header_list; }
|
||||
virtual void set_header_list(GC::Ref<HeaderList> header_list) { m_header_list = header_list; }
|
||||
|
|
@ -90,8 +90,8 @@ public:
|
|||
[[nodiscard]] virtual Optional<CacheState> const& cache_state() const { return m_cache_state; }
|
||||
virtual void set_cache_state(Optional<CacheState> cache_state) { m_cache_state = move(cache_state); }
|
||||
|
||||
[[nodiscard]] virtual Vector<ByteBuffer> const& cors_exposed_header_name_list() const { return m_cors_exposed_header_name_list; }
|
||||
virtual void set_cors_exposed_header_name_list(Vector<ByteBuffer> cors_exposed_header_name_list) { m_cors_exposed_header_name_list = move(cors_exposed_header_name_list); }
|
||||
[[nodiscard]] virtual Vector<ByteString> const& cors_exposed_header_name_list() const { return m_cors_exposed_header_name_list; }
|
||||
virtual void set_cors_exposed_header_name_list(Vector<ByteString> cors_exposed_header_name_list) { m_cors_exposed_header_name_list = move(cors_exposed_header_name_list); }
|
||||
|
||||
[[nodiscard]] virtual bool range_requested() const { return m_range_requested; }
|
||||
virtual void set_range_requested(bool range_requested) { m_range_requested = range_requested; }
|
||||
|
|
@ -103,10 +103,10 @@ public:
|
|||
virtual void set_timing_allow_passed(bool timing_allow_passed) { m_timing_allow_passed = timing_allow_passed; }
|
||||
|
||||
[[nodiscard]] virtual BodyInfo const& body_info() const { return m_body_info; }
|
||||
virtual void set_body_info(BodyInfo body_info) { m_body_info = body_info; }
|
||||
virtual void set_body_info(BodyInfo body_info) { m_body_info = move(body_info); }
|
||||
|
||||
[[nodiscard]] RedirectTaint redirect_taint() const { return m_redirect_taint; }
|
||||
void set_redirect_taint(RedirectTaint redirect_taint) { m_redirect_taint = move(redirect_taint); }
|
||||
void set_redirect_taint(RedirectTaint redirect_taint) { m_redirect_taint = redirect_taint; }
|
||||
|
||||
[[nodiscard]] bool is_aborted_network_error() const;
|
||||
[[nodiscard]] bool is_network_error() const;
|
||||
|
|
@ -153,7 +153,7 @@ private:
|
|||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-status-message
|
||||
// A response has an associated status message. Unless stated otherwise it is the empty byte sequence.
|
||||
ByteBuffer m_status_message;
|
||||
ByteString m_status_message;
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-header-list
|
||||
// A response has an associated header list (a header list). Unless stated otherwise it is empty.
|
||||
|
|
@ -169,7 +169,7 @@ private:
|
|||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list
|
||||
// A response has an associated CORS-exposed header-name list (a list of zero or more header names). The list is empty unless otherwise specified.
|
||||
Vector<ByteBuffer> m_cors_exposed_header_name_list;
|
||||
Vector<ByteString> m_cors_exposed_header_name_list;
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-response-range-requested-flag
|
||||
// A response has an associated range-requested flag, which is initially unset.
|
||||
|
|
@ -200,14 +200,14 @@ private:
|
|||
u64 stale_while_revalidate_lifetime() const;
|
||||
|
||||
// Non-standard
|
||||
ByteBuffer m_method;
|
||||
ByteString m_method;
|
||||
MonotonicTime m_response_time;
|
||||
|
||||
Optional<String> m_network_error_message;
|
||||
|
||||
public:
|
||||
[[nodiscard]] ByteBuffer const& method() const { return m_method; }
|
||||
void set_method(ByteBuffer method) { m_method = move(method); }
|
||||
[[nodiscard]] ByteString const& method() const { return m_method; }
|
||||
void set_method(ByteString method) { m_method = move(method); }
|
||||
};
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-filtered-response
|
||||
|
|
@ -230,8 +230,8 @@ public:
|
|||
[[nodiscard]] virtual Status status() const override { return m_internal_response->status(); }
|
||||
virtual void set_status(Status status) override { m_internal_response->set_status(status); }
|
||||
|
||||
[[nodiscard]] virtual ReadonlyBytes status_message() const LIFETIME_BOUND override { return m_internal_response->status_message(); }
|
||||
virtual void set_status_message(ByteBuffer status_message) override { m_internal_response->set_status_message(move(status_message)); }
|
||||
[[nodiscard]] virtual ByteString const& status_message() const override { return m_internal_response->status_message(); }
|
||||
virtual void set_status_message(ByteString status_message) override { m_internal_response->set_status_message(move(status_message)); }
|
||||
|
||||
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const override { return m_internal_response->header_list(); }
|
||||
virtual void set_header_list(GC::Ref<HeaderList> header_list) override { m_internal_response->set_header_list(header_list); }
|
||||
|
|
@ -242,8 +242,8 @@ public:
|
|||
[[nodiscard]] virtual Optional<CacheState> const& cache_state() const override { return m_internal_response->cache_state(); }
|
||||
virtual void set_cache_state(Optional<CacheState> cache_state) override { m_internal_response->set_cache_state(move(cache_state)); }
|
||||
|
||||
[[nodiscard]] virtual Vector<ByteBuffer> const& cors_exposed_header_name_list() const override { return m_internal_response->cors_exposed_header_name_list(); }
|
||||
virtual void set_cors_exposed_header_name_list(Vector<ByteBuffer> cors_exposed_header_name_list) override { m_internal_response->set_cors_exposed_header_name_list(move(cors_exposed_header_name_list)); }
|
||||
[[nodiscard]] virtual Vector<ByteString> const& cors_exposed_header_name_list() const override { return m_internal_response->cors_exposed_header_name_list(); }
|
||||
virtual void set_cors_exposed_header_name_list(Vector<ByteString> cors_exposed_header_name_list) override { m_internal_response->set_cors_exposed_header_name_list(move(cors_exposed_header_name_list)); }
|
||||
|
||||
[[nodiscard]] virtual bool range_requested() const override { return m_internal_response->range_requested(); }
|
||||
virtual void set_range_requested(bool range_requested) override { m_internal_response->set_range_requested(range_requested); }
|
||||
|
|
@ -317,7 +317,7 @@ public:
|
|||
[[nodiscard]] virtual Vector<URL::URL> const& url_list() const override { return m_url_list; }
|
||||
[[nodiscard]] virtual Vector<URL::URL>& url_list() override { return m_url_list; }
|
||||
[[nodiscard]] virtual Status status() const override { return 0; }
|
||||
[[nodiscard]] virtual ReadonlyBytes status_message() const LIFETIME_BOUND override { return {}; }
|
||||
[[nodiscard]] virtual ByteString const& status_message() const override { return m_method; }
|
||||
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const override { return m_header_list; }
|
||||
[[nodiscard]] virtual GC::Ptr<Body> body() const override { return nullptr; }
|
||||
|
||||
|
|
@ -327,6 +327,7 @@ private:
|
|||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
Vector<URL::URL> m_url_list;
|
||||
ByteString const m_method;
|
||||
GC::Ref<HeaderList> m_header_list;
|
||||
};
|
||||
|
||||
|
|
@ -340,7 +341,7 @@ public:
|
|||
|
||||
[[nodiscard]] virtual Type type() const override { return Type::OpaqueRedirect; }
|
||||
[[nodiscard]] virtual Status status() const override { return 0; }
|
||||
[[nodiscard]] virtual ReadonlyBytes status_message() const LIFETIME_BOUND override { return {}; }
|
||||
[[nodiscard]] virtual ByteString const& status_message() const override { return m_method; }
|
||||
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const override { return m_header_list; }
|
||||
[[nodiscard]] virtual GC::Ptr<Body> body() const override { return nullptr; }
|
||||
|
||||
|
|
@ -349,6 +350,7 @@ private:
|
|||
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
ByteString const m_method;
|
||||
GC::Ref<HeaderList> m_header_list;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
|
|||
|
||||
// method
|
||||
// request’s method.
|
||||
request->set_method(MUST(ByteBuffer::copy(input_request->method())));
|
||||
request->set_method(input_request->method());
|
||||
|
||||
// header list
|
||||
// A copy of request’s header list.
|
||||
|
|
@ -369,16 +369,16 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
|
|||
auto method = *init.method;
|
||||
|
||||
// 2. If method is not a method or method is a forbidden method, then throw a TypeError.
|
||||
if (!Infrastructure::is_method(method.bytes()))
|
||||
if (!Infrastructure::is_method(method))
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method has invalid value"sv };
|
||||
if (Infrastructure::is_forbidden_method(method.bytes()))
|
||||
if (Infrastructure::is_forbidden_method(method))
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method must not be one of CONNECT, TRACE, or TRACK"sv };
|
||||
|
||||
// 3. Normalize method.
|
||||
method = MUST(String::from_utf8(Infrastructure::normalize_method(method.bytes())));
|
||||
auto normalized_method = Infrastructure::normalize_method(method);
|
||||
|
||||
// 4. Set request’s method to method.
|
||||
request->set_method(MUST(ByteBuffer::copy(method.bytes())));
|
||||
request->set_method(move(normalized_method));
|
||||
}
|
||||
|
||||
// 26. If init["signal"] exists, then set signal to it.
|
||||
|
|
@ -431,7 +431,7 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
|
|||
// 4. If headers is a Headers object, then for each header of its header list, append header to this’s headers.
|
||||
if (auto* header_list = headers.get_pointer<GC::Ref<Infrastructure::HeaderList>>()) {
|
||||
for (auto& header : *header_list->ptr())
|
||||
TRY(request_object->headers()->append(Infrastructure::Header::from_string_pair(header.name, header.value)));
|
||||
TRY(request_object->headers()->append(Infrastructure::Header::isomorphic_encode(header.name, header.value)));
|
||||
}
|
||||
// 5. Otherwise, fill this’s headers with headers.
|
||||
else {
|
||||
|
|
@ -445,7 +445,7 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
|
|||
input_body = input.get<GC::Root<Request>>()->request()->body();
|
||||
|
||||
// 35. If either init["body"] exists and is non-null or inputBody is non-null, and request’s method is `GET` or `HEAD`, then throw a TypeError.
|
||||
if (((init.body.has_value() && (*init.body).has_value()) || (input_body.has_value() && !input_body.value().has<Empty>())) && StringView { request->method() }.is_one_of("GET"sv, "HEAD"sv))
|
||||
if (((init.body.has_value() && (*init.body).has_value()) || (input_body.has_value() && !input_body.value().has<Empty>())) && request->method().is_one_of("GET"sv, "HEAD"sv))
|
||||
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Method must not be GET or HEAD when body is provided"sv };
|
||||
|
||||
// 36. Let initBody be null.
|
||||
|
|
@ -463,8 +463,8 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
|
|||
auto const& type = body_with_type.type;
|
||||
|
||||
// 4. If type is non-null and this’s headers’s header list does not contain `Content-Type`, then append (`Content-Type`, type) to this’s headers.
|
||||
if (type.has_value() && !request_object->headers()->header_list()->contains("Content-Type"sv.bytes()))
|
||||
TRY(request_object->headers()->append(Infrastructure::Header::from_string_pair("Content-Type"sv, type->span())));
|
||||
if (type.has_value() && !request_object->headers()->header_list()->contains("Content-Type"sv))
|
||||
TRY(request_object->headers()->append(Infrastructure::Header::isomorphic_encode("Content-Type"sv, *type)));
|
||||
}
|
||||
|
||||
// 38. Let inputOrInitBody be initBody if it is non-null; otherwise inputBody.
|
||||
|
|
@ -508,7 +508,7 @@ WebIDL::ExceptionOr<GC::Ref<Request>> Request::construct_impl(JS::Realm& realm,
|
|||
String Request::method() const
|
||||
{
|
||||
// The method getter steps are to return this’s request’s method.
|
||||
return MUST(String::from_utf8(m_request->method()));
|
||||
return MUST(String::from_byte_string(m_request->method()));
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-request-url
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ WebIDL::ExceptionOr<void> Response::initialize_response(ResponseInit const& init
|
|||
m_response->set_status(init.status);
|
||||
|
||||
// 4. Set response’s response’s status message to init["statusText"].
|
||||
m_response->set_status_message(MUST(ByteBuffer::copy(init.status_text.bytes())));
|
||||
m_response->set_status_message(init.status_text.to_byte_string());
|
||||
|
||||
// 5. If init["headers"] exists, then fill response’s headers with init["headers"].
|
||||
if (init.headers.has_value())
|
||||
|
|
@ -127,13 +127,8 @@ WebIDL::ExceptionOr<void> Response::initialize_response(ResponseInit const& init
|
|||
m_response->set_body(body->body);
|
||||
|
||||
// 3. If body’s type is non-null and response’s header list does not contain `Content-Type`, then append (`Content-Type`, body’s type) to response’s header list.
|
||||
if (body->type.has_value() && !m_response->header_list()->contains("Content-Type"sv.bytes())) {
|
||||
auto header = Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy("Content-Type"sv.bytes())),
|
||||
.value = MUST(ByteBuffer::copy(body->type->span())),
|
||||
};
|
||||
m_response->header_list()->append(move(header));
|
||||
}
|
||||
if (body->type.has_value() && !m_response->header_list()->contains("Content-Type"sv))
|
||||
m_response->header_list()->append({ "Content-Type"sv, *body->type });
|
||||
}
|
||||
|
||||
return {};
|
||||
|
|
@ -204,7 +199,7 @@ WebIDL::ExceptionOr<GC::Ref<Response>> Response::redirect(JS::VM& vm, String con
|
|||
auto value = parsed_url->serialize();
|
||||
|
||||
// 7. Append (`Location`, value) to responseObject’s response’s header list.
|
||||
auto header = Infrastructure::Header::from_string_pair("Location"sv, value);
|
||||
auto header = Infrastructure::Header::isomorphic_encode("Location"sv, value);
|
||||
response_object->response()->header_list()->append(move(header));
|
||||
|
||||
// 8. Return responseObject.
|
||||
|
|
@ -229,7 +224,7 @@ WebIDL::ExceptionOr<GC::Ref<Response>> Response::json(JS::VM& vm, JS::Value data
|
|||
// 4. Perform initialize a response given responseObject, init, and (body, "application/json").
|
||||
auto body_with_type = Infrastructure::BodyWithType {
|
||||
.body = body,
|
||||
.type = MUST(ByteBuffer::copy("application/json"sv.bytes()))
|
||||
.type = "application/json"sv,
|
||||
};
|
||||
TRY(response_object->initialize_response(init, move(body_with_type)));
|
||||
|
||||
|
|
@ -278,7 +273,7 @@ bool Response::ok() const
|
|||
String Response::status_text() const
|
||||
{
|
||||
// The statusText getter steps are to return this’s response’s status message.
|
||||
return MUST(String::from_utf8(m_response->status_message()));
|
||||
return MUST(String::from_byte_string(m_response->status_message()));
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-response-headers
|
||||
|
|
|
|||
|
|
@ -69,8 +69,7 @@ WebIDL::ExceptionOr<GC::Ref<EventSource>> EventSource::construct_impl(JS::Realm&
|
|||
request->set_client(&settings);
|
||||
|
||||
// 10. User agents may set (`Accept`, `text/event-stream`) in request's header list.
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Accept"sv, "text/event-stream"sv);
|
||||
request->header_list()->set(move(header));
|
||||
request->header_list()->set({ "Accept"sv, "text/event-stream"sv });
|
||||
|
||||
// 11. Set request's cache mode to "no-store".
|
||||
request->set_cache_mode(Fetch::Infrastructure::Request::CacheMode::NoStore);
|
||||
|
|
@ -319,8 +318,8 @@ void EventSource::reestablish_the_connection()
|
|||
if (!m_last_event_id.is_empty()) {
|
||||
// 1. Let lastEventIDValue be the EventSource object's last event ID string, encoded as UTF-8.
|
||||
// 2. Set (`Last-Event-ID`, lastEventIDValue) in request's header list.
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Last-Event-ID"sv, m_last_event_id);
|
||||
request->header_list()->set(header);
|
||||
auto header = Fetch::Infrastructure::Header::isomorphic_encode("Last-Event-ID"sv, m_last_event_id);
|
||||
request->header_list()->set(move(header));
|
||||
}
|
||||
|
||||
// 4. Fetch request and process the response obtained in this fashion, if any, as described earlier in this section.
|
||||
|
|
|
|||
|
|
@ -794,10 +794,7 @@ static GC::Ref<NavigationParams> create_navigation_params_from_a_srcdoc_resource
|
|||
// body: the UTF-8 encoding of documentResource, as a body
|
||||
auto response = Fetch::Infrastructure::Response::create(vm);
|
||||
response->url_list().append(URL::about_srcdoc());
|
||||
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv);
|
||||
response->header_list()->append(move(header));
|
||||
|
||||
response->header_list()->append({ "Content-Type"sv, "text/html"sv });
|
||||
response->set_body(Fetch::Infrastructure::byte_sequence_as_body(realm, document_resource.get<String>().bytes()));
|
||||
|
||||
// 3. Let responseOrigin be the result of determining the origin given response's URL, targetSnapshotParams's sandboxing flags, and entry's document state's origin.
|
||||
|
|
@ -1128,7 +1125,7 @@ static void create_navigation_params_by_fetching(GC::Ptr<SessionHistoryEntry> en
|
|||
// 6. If documentResource is a POST resource:
|
||||
if (auto* post_resource = document_resource.get_pointer<POSTResource>()) {
|
||||
// 1. Set request's method to `POST`.
|
||||
request->set_method(MUST(ByteBuffer::copy("POST"sv.bytes())));
|
||||
request->set_method("POST"sv);
|
||||
|
||||
// 2. Set request's body to documentResource's request body.
|
||||
request->set_body(document_resource.get<POSTResource>().request_body.value());
|
||||
|
|
@ -1157,7 +1154,7 @@ static void create_navigation_params_by_fetching(GC::Ptr<SessionHistoryEntry> en
|
|||
request_content_type = request_content_type_buffer.string_view();
|
||||
}
|
||||
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, request_content_type);
|
||||
auto header = Fetch::Infrastructure::Header::isomorphic_encode("Content-Type"sv, request_content_type);
|
||||
request->header_list()->append(move(header));
|
||||
}
|
||||
|
||||
|
|
@ -2043,10 +2040,7 @@ GC::Ptr<DOM::Document> Navigable::evaluate_javascript_url(URL::URL const& url, U
|
|||
// body: the UTF-8 encoding of result, as a body
|
||||
auto response = Fetch::Infrastructure::Response::create(vm);
|
||||
response->url_list().append(active_document()->url());
|
||||
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv);
|
||||
response->header_list()->append(move(header));
|
||||
|
||||
response->header_list()->append({ "Content-Type"sv, "text/html"sv });
|
||||
response->set_body(Fetch::Infrastructure::byte_sequence_as_body(realm, result.bytes()));
|
||||
|
||||
// 12. Let policyContainer be targetNavigable's active document's policy container.
|
||||
|
|
|
|||
|
|
@ -61,12 +61,12 @@ WebIDL::ExceptionOr<bool> NavigatorBeaconPartial::send_beacon(String const& url,
|
|||
cors_mode = Fetch::Infrastructure::Request::Mode::CORS;
|
||||
|
||||
// If contentType value is a CORS-safelisted request-header value for the Content-Type header, set corsMode to "no-cors".
|
||||
auto content_type_header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, content_type.value());
|
||||
auto content_type_header = Fetch::Infrastructure::Header::isomorphic_encode("Content-Type"sv, content_type.value());
|
||||
if (Fetch::Infrastructure::is_cors_safelisted_request_header(content_type_header))
|
||||
cors_mode = Fetch::Infrastructure::Request::Mode::NoCORS;
|
||||
|
||||
// Append a Content-Type header with value contentType to headerList.
|
||||
header_list->append(content_type_header);
|
||||
header_list->append(move(content_type_header));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,12 +74,12 @@ WebIDL::ExceptionOr<bool> NavigatorBeaconPartial::send_beacon(String const& url,
|
|||
|
||||
// 7.1 Let req be a new request, initialized as follows:
|
||||
auto req = Fetch::Infrastructure::Request::create(vm);
|
||||
req->set_method(MUST(ByteBuffer::copy("POST"sv.bytes()))); // method: POST
|
||||
req->set_client(&relevant_settings_object); // client: this's relevant settings object
|
||||
req->set_url_list({ parsed_url.release_value() }); // url: parsedUrl
|
||||
req->set_header_list(header_list); // header list: headerList
|
||||
req->set_origin(origin); // origin: origin
|
||||
req->set_keepalive(true); // keepalive: true
|
||||
req->set_method("POST"sv); // method: POST
|
||||
req->set_client(&relevant_settings_object); // client: this's relevant settings object
|
||||
req->set_url_list({ parsed_url.release_value() }); // url: parsedUrl
|
||||
req->set_header_list(header_list); // header list: headerList
|
||||
req->set_origin(origin); // origin: origin
|
||||
req->set_keepalive(true); // keepalive: true
|
||||
if (transmitted_data)
|
||||
req->set_body(GC::Ref<Fetch::Infrastructure::Body> { *transmitted_data }); // body: transmittedData
|
||||
req->set_mode(cors_mode); // mode: corsMode
|
||||
|
|
|
|||
|
|
@ -157,31 +157,35 @@ ErrorOr<String> convert_to_scalar_value_string(StringView string)
|
|||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#isomorphic-encode
|
||||
ByteBuffer isomorphic_encode(StringView input)
|
||||
ByteString isomorphic_encode(StringView input)
|
||||
{
|
||||
// To isomorphic encode an isomorphic string input: return a byte sequence whose length is equal to input’s code
|
||||
// point length and whose bytes have the same values as the values of input’s code points, in the same order.
|
||||
// NOTE: This is essentially spec-speak for "Encode as ISO-8859-1 / Latin-1".
|
||||
ByteBuffer buf = {};
|
||||
// NB: This is essentially spec-speak for "Encode as ISO-8859-1 / Latin-1".
|
||||
StringBuilder builder(input.length());
|
||||
|
||||
for (auto code_point : Utf8View { input }) {
|
||||
// VERIFY(code_point <= 0xFF);
|
||||
if (code_point > 0xFF)
|
||||
dbgln("FIXME: Trying to isomorphic encode a string with code points > U+00FF.");
|
||||
buf.append((u8)code_point);
|
||||
|
||||
builder.append(static_cast<u8>(code_point));
|
||||
}
|
||||
return buf;
|
||||
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
// https://infra.spec.whatwg.org/#isomorphic-decode
|
||||
String isomorphic_decode(ReadonlyBytes input)
|
||||
String isomorphic_decode(StringView input)
|
||||
{
|
||||
// To isomorphic decode a byte sequence input, return a string whose code point length is equal
|
||||
// to input’s length and whose code points have the same values as the values of input’s bytes, in the same order.
|
||||
// NOTE: This is essentially spec-speak for "Decode as ISO-8859-1 / Latin-1".
|
||||
StringBuilder builder(input.size());
|
||||
for (u8 code_point : input) {
|
||||
builder.append_code_point(code_point);
|
||||
}
|
||||
// To isomorphic decode a byte sequence input, return a string whose code point length is equal to input’s length
|
||||
// and whose code points have the same values as the values of input’s bytes, in the same order.
|
||||
// NB: This is essentially spec-speak for "Decode as ISO-8859-1 / Latin-1".
|
||||
StringBuilder builder(input.length());
|
||||
|
||||
for (auto byte : input.bytes())
|
||||
builder.append_code_point(byte);
|
||||
|
||||
return builder.to_string_without_validation();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ WEB_API ErrorOr<String> strip_and_collapse_whitespace(StringView string);
|
|||
Utf16String strip_and_collapse_whitespace(Utf16String const& string);
|
||||
WEB_API bool is_code_unit_prefix(StringView potential_prefix, StringView input);
|
||||
WEB_API ErrorOr<String> convert_to_scalar_value_string(StringView string);
|
||||
ByteBuffer isomorphic_encode(StringView input);
|
||||
WEB_API String isomorphic_decode(ReadonlyBytes input);
|
||||
ByteString isomorphic_encode(StringView input);
|
||||
WEB_API String isomorphic_decode(StringView input);
|
||||
bool code_unit_less_than(StringView a, StringView b);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ namespace Web::ReferrerPolicy {
|
|||
ReferrerPolicy parse_a_referrer_policy_from_a_referrer_policy_header(Fetch::Infrastructure::Response const& response)
|
||||
{
|
||||
// 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
|
||||
auto policy_tokens_or_failure = response.header_list()->extract_header_list_values("Referrer-Policy"sv.bytes());
|
||||
auto policy_tokens_or_failure = response.header_list()->extract_header_list_values("Referrer-Policy"sv);
|
||||
|
||||
// 2. Let policy be the empty string.
|
||||
auto policy = ReferrerPolicy::EmptyString;
|
||||
|
||||
// 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
|
||||
if (auto const* policy_tokens = policy_tokens_or_failure.get_pointer<Vector<ByteBuffer>>()) {
|
||||
if (auto const* policy_tokens = policy_tokens_or_failure.get_pointer<Vector<ByteString>>()) {
|
||||
for (auto const& token : *policy_tokens) {
|
||||
auto referrer_policy = from_string(token);
|
||||
if (referrer_policy.has_value() && referrer_policy.value() != ReferrerPolicy::EmptyString)
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ static void update(JS::VM& vm, GC::Ref<Job> job)
|
|||
|
||||
// 1. Append `Service-Worker`/`script` to request’s header list.
|
||||
// Note: See https://w3c.github.io/ServiceWorker/#service-worker
|
||||
request->header_list()->append(Fetch::Infrastructure::Header::from_string_pair("Service-Worker"sv, "script"sv));
|
||||
request->header_list()->append(Fetch::Infrastructure::Header::isomorphic_encode("Service-Worker"sv, "script"sv));
|
||||
|
||||
// 2. Set request’s cache mode to "no-cache" if any of the following are true:
|
||||
// - registration’s update via cache mode is not "all".
|
||||
|
|
@ -266,7 +266,7 @@ static void update(JS::VM& vm, GC::Ref<Job> job)
|
|||
|
||||
// 8. Let serviceWorkerAllowed be the result of extracting header list values given `Service-Worker-Allowed` and response’s header list.
|
||||
// Note: See the definition of the Service-Worker-Allowed header in Appendix B: Extended HTTP headers. https://w3c.github.io/ServiceWorker/#service-worker-allowed
|
||||
auto service_worker_allowed = response->header_list()->extract_header_list_values("Service-Worker-Allowed"sv.bytes());
|
||||
auto service_worker_allowed = response->header_list()->extract_header_list_values("Service-Worker-Allowed"sv);
|
||||
|
||||
// 9. Set policyContainer to the result of creating a policy container from a fetch response given response.
|
||||
// FIXME: CSP not implemented yet
|
||||
|
|
@ -304,7 +304,7 @@ static void update(JS::VM& vm, GC::Ref<Job> job)
|
|||
// 14. Else:
|
||||
else {
|
||||
// 1. Let maxScope be the result of parsing serviceWorkerAllowed using job’s script url as the base URL.
|
||||
auto max_scope = DOMURL::parse(service_worker_allowed.get<Vector<ByteBuffer>>()[0], job->script_url);
|
||||
auto max_scope = DOMURL::parse(service_worker_allowed.get<Vector<ByteString>>()[0], job->script_url);
|
||||
|
||||
// 2. If maxScope’s origin is job’s script url's origin, then:
|
||||
if (max_scope->origin().is_same_origin(job->script_url.origin())) {
|
||||
|
|
|
|||
|
|
@ -411,13 +411,10 @@ Optional<StringView> XMLHttpRequest::get_final_encoding() const
|
|||
}
|
||||
|
||||
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-setrequestheader
|
||||
WebIDL::ExceptionOr<void> XMLHttpRequest::set_request_header(String const& name_string, String const& value_string)
|
||||
WebIDL::ExceptionOr<void> XMLHttpRequest::set_request_header(String const& name, String const& value)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
||||
auto name = name_string.bytes();
|
||||
auto value = value_string.bytes();
|
||||
|
||||
// 1. If this’s state is not opened, then throw an "InvalidStateError" DOMException.
|
||||
if (m_state != State::Opened)
|
||||
return WebIDL::InvalidStateError::create(realm, "XHR readyState is not OPENED"_utf16);
|
||||
|
|
@ -436,7 +433,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::set_request_header(String const& name_
|
|||
return WebIDL::SyntaxError::create(realm, "Header value contains invalid characters."_utf16);
|
||||
|
||||
auto header = Fetch::Infrastructure::Header {
|
||||
.name = MUST(ByteBuffer::copy(name)),
|
||||
.name = name.to_byte_string(),
|
||||
.value = move(normalized_value),
|
||||
};
|
||||
|
||||
|
|
@ -451,16 +448,14 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::set_request_header(String const& name_
|
|||
}
|
||||
|
||||
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-open
|
||||
WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method_string, String const& url)
|
||||
WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method, String const& url)
|
||||
{
|
||||
// 7. If the async argument is omitted, set async to true, and set username and password to null.
|
||||
return open(method_string, url, true, Optional<String> {}, Optional<String> {});
|
||||
return open(method, url, true, {}, {});
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method_string, String const& url, bool async, Optional<String> const& username, Optional<String> const& password)
|
||||
WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method, String const& url, bool async, Optional<String> const& username, Optional<String> const& password)
|
||||
{
|
||||
auto method = method_string.bytes();
|
||||
|
||||
// 1. If this’s relevant global object is a Window object and its associated Document is not fully active, then throw an "InvalidStateError" DOMException.
|
||||
if (is<HTML::Window>(HTML::relevant_global_object(*this))) {
|
||||
auto const& window = static_cast<HTML::Window const&>(HTML::relevant_global_object(*this));
|
||||
|
|
@ -520,7 +515,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::open(String const& method_string, Stri
|
|||
// Unset this’s upload listener flag.
|
||||
m_upload_listener = false;
|
||||
// Set this’s request method to method.
|
||||
m_request_method = normalized_method.span();
|
||||
m_request_method = move(normalized_method);
|
||||
// Set this’s request URL to parsedURL.
|
||||
m_request_url = parsed_url.release_value();
|
||||
// Set this’s synchronous flag if async is false; otherwise unset this’s synchronous flag.
|
||||
|
|
@ -568,7 +563,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
|
|||
// 4. If body is not null, then:
|
||||
if (body.has_value()) {
|
||||
// 1. Let extractedContentType be null.
|
||||
Optional<ByteBuffer> extracted_content_type;
|
||||
Optional<ByteString> extracted_content_type;
|
||||
|
||||
// 2. If body is a Document, then set this’s request body to body, serialized, converted, and UTF-8 encoded.
|
||||
if (body->has<GC::Root<DOM::Document>>()) {
|
||||
|
|
@ -589,7 +584,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
|
|||
}
|
||||
|
||||
// 4. Let originalAuthorContentType be the result of getting `Content-Type` from this’s author request headers.
|
||||
auto original_author_content_type = m_author_request_headers->get("Content-Type"sv.bytes());
|
||||
auto original_author_content_type = m_author_request_headers->get("Content-Type"sv);
|
||||
|
||||
// 5. If originalAuthorContentType is non-null, then:
|
||||
if (original_author_content_type.has_value()) {
|
||||
|
|
@ -609,7 +604,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
|
|||
auto new_content_type_serialized = content_type_record->serialized();
|
||||
|
||||
// 3. Set (`Content-Type`, newContentTypeSerialized) in this’s author request headers.
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, new_content_type_serialized);
|
||||
auto header = Fetch::Infrastructure::Header::isomorphic_encode("Content-Type"sv, new_content_type_serialized);
|
||||
m_author_request_headers->set(move(header));
|
||||
}
|
||||
}
|
||||
|
|
@ -623,20 +618,18 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
|
|||
// NOTE: A document can only be an HTML document or XML document.
|
||||
// 1. If body is an HTML document, then set (`Content-Type`, `text/html;charset=UTF-8`) in this’s author request headers.
|
||||
if (document->is_html_document()) {
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html;charset=UTF-8"sv);
|
||||
m_author_request_headers->set(move(header));
|
||||
m_author_request_headers->set({ "Content-Type"sv, "text/html;charset=UTF-8"sv });
|
||||
}
|
||||
// 2. Otherwise, if body is an XML document, set (`Content-Type`, `application/xml;charset=UTF-8`) in this’s author request headers.
|
||||
else if (document->is_xml_document()) {
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "application/xml;charset=UTF-8"sv);
|
||||
m_author_request_headers->set(move(header));
|
||||
m_author_request_headers->set({ "Content-Type"sv, "application/xml;charset=UTF-8"sv });
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
// 3. Otherwise, if extractedContentType is not null, set (`Content-Type`, extractedContentType) in this’s author request headers.
|
||||
else if (extracted_content_type.has_value()) {
|
||||
auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, extracted_content_type.value());
|
||||
auto header = Fetch::Infrastructure::Header::isomorphic_encode("Content-Type"sv, extracted_content_type.value());
|
||||
m_author_request_headers->set(move(header));
|
||||
}
|
||||
}
|
||||
|
|
@ -650,7 +643,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
|
|||
|
||||
// method
|
||||
// This’s request method.
|
||||
request->set_method(MUST(ByteBuffer::copy(m_request_method.bytes())));
|
||||
request->set_method(m_request_method);
|
||||
|
||||
// URL
|
||||
// This’s request URL.
|
||||
|
|
@ -970,7 +963,7 @@ void XMLHttpRequest::set_onreadystatechange(WebIDL::CallbackType* value)
|
|||
Optional<String> XMLHttpRequest::get_response_header(String const& name) const
|
||||
{
|
||||
// The getResponseHeader(name) method steps are to return the result of getting name from this’s response’s header list.
|
||||
auto header_bytes = m_response->header_list()->get(name.bytes());
|
||||
auto header_bytes = m_response->header_list()->get(name);
|
||||
if (!header_bytes.has_value())
|
||||
return {};
|
||||
|
||||
|
|
@ -979,18 +972,16 @@ Optional<String> XMLHttpRequest::get_response_header(String const& name) const
|
|||
}
|
||||
|
||||
// https://xhr.spec.whatwg.org/#legacy-uppercased-byte-less-than
|
||||
static ErrorOr<bool> is_legacy_uppercased_byte_less_than(ReadonlyBytes a, ReadonlyBytes b)
|
||||
static bool is_legacy_uppercased_byte_less_than(ByteString const& a, ByteString const& b)
|
||||
{
|
||||
// 1. Let A be a, byte-uppercased.
|
||||
auto uppercased_a = TRY(ByteBuffer::copy(a));
|
||||
Infra::byte_uppercase(uppercased_a);
|
||||
auto uppercased_a = a.to_uppercase();
|
||||
|
||||
// 2. Let B be b, byte-uppercased.
|
||||
auto uppercased_b = TRY(ByteBuffer::copy(b));
|
||||
Infra::byte_uppercase(uppercased_b);
|
||||
auto uppercased_b = b.to_uppercase();
|
||||
|
||||
// 3. Return A is byte less than B.
|
||||
return Infra::is_byte_less_than(uppercased_a, uppercased_b);
|
||||
return Infra::is_byte_less_than(uppercased_a.bytes(), uppercased_b.bytes());
|
||||
}
|
||||
|
||||
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-getallresponseheaders
|
||||
|
|
@ -1003,22 +994,15 @@ String XMLHttpRequest::get_all_response_headers() const
|
|||
auto initial_headers = m_response->header_list()->sort_and_combine();
|
||||
|
||||
// 3. Let headers be the result of sorting initialHeaders in ascending order, with a being less than b if a’s name is legacy-uppercased-byte less than b’s name.
|
||||
// Spec Note: Unfortunately, this is needed for compatibility with deployed content.
|
||||
// NOTE: quick_sort mutates the collection instead of returning a sorted copy.
|
||||
// NOTE: Unfortunately, this is needed for compatibility with deployed content.
|
||||
quick_sort(initial_headers, [](Fetch::Infrastructure::Header const& a, Fetch::Infrastructure::Header const& b) {
|
||||
return MUST(is_legacy_uppercased_byte_less_than(a.name, b.name));
|
||||
return is_legacy_uppercased_byte_less_than(a.name, b.name);
|
||||
});
|
||||
|
||||
// 4. For each header in headers, append header’s name, followed by a 0x3A 0x20 byte pair, followed by header’s value, followed by a 0x0D 0x0A byte pair, to output.
|
||||
for (auto const& header : initial_headers) {
|
||||
output.append(header.name);
|
||||
output.append(0x3A); // ':'
|
||||
output.append(0x20); // ' '
|
||||
// FIXME: The spec does not mention isomorphic decode. Spec bug?
|
||||
auto decoder_header = Infra::isomorphic_decode(header.value);
|
||||
output.append(decoder_header.bytes());
|
||||
output.append(0x0D); // '\r'
|
||||
output.append(0x0A); // '\n'
|
||||
output.appendff("{}: {}\r\n", header.name, Infra::isomorphic_decode(header.value));
|
||||
}
|
||||
|
||||
// 5. Return output.
|
||||
|
|
@ -1157,10 +1141,8 @@ Fetch::Infrastructure::Status XMLHttpRequest::status() const
|
|||
// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-statustext
|
||||
WebIDL::ExceptionOr<String> XMLHttpRequest::status_text() const
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// The statusText getter steps are to return this’s response’s status message.
|
||||
return TRY_OR_THROW_OOM(vm, String::from_utf8(m_response->status_message()));
|
||||
return MUST(String::from_byte_string(m_response->status_message()));
|
||||
}
|
||||
|
||||
// https://xhr.spec.whatwg.org/#handle-response-end-of-body
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public:
|
|||
WebIDL::ExceptionOr<void> open(String const& method, String const& url, bool async, Optional<String> const& username = Optional<String> {}, Optional<String> const& password = Optional<String> {});
|
||||
WebIDL::ExceptionOr<void> send(Optional<DocumentOrXMLHttpRequestBodyInit> body);
|
||||
|
||||
WebIDL::ExceptionOr<void> set_request_header(String const& header, String const& value);
|
||||
WebIDL::ExceptionOr<void> set_request_header(String const& name, String const& value);
|
||||
WebIDL::ExceptionOr<void> set_response_type(Bindings::XMLHttpRequestResponseType);
|
||||
|
||||
Optional<String> get_response_header(String const& name) const;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue