ladybird/Userland/Libraries/LibWeb/Fetch/Response.cpp
2024-04-28 12:32:04 +02:00

294 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Completion.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Bindings/ResponsePrototype.h>
#include <LibWeb/DOMURL/DOMURL.h>
#include <LibWeb/Fetch/Enums.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
#include <LibWeb/Fetch/Response.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/Infra/JSON.h>
namespace Web::Fetch {
JS_DEFINE_ALLOCATOR(Response);
Response::Response(JS::Realm& realm, JS::NonnullGCPtr<Infrastructure::Response> response)
: PlatformObject(realm)
, m_response(response)
{
}
Response::~Response() = default;
void Response::initialize(JS::Realm& realm)
{
Base::initialize(realm);
WEB_SET_PROTOTYPE_FOR_INTERFACE(Response);
}
void Response::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_response);
visitor.visit(m_headers);
}
// https://fetch.spec.whatwg.org/#concept-body-mime-type
// https://fetch.spec.whatwg.org/#ref-for-concept-header-extract-mime-type%E2%91%A7
Optional<MimeSniff::MimeType> Response::mime_type_impl() const
{
// Objects including the Body interface mixin need to define an associated MIME type algorithm which takes no arguments and returns failure or a MIME type.
// A Response objects MIME type is to return the result of extracting a MIME type from its responses header list.
return m_response->header_list()->extract_mime_type();
}
// https://fetch.spec.whatwg.org/#concept-body-body
// https://fetch.spec.whatwg.org/#ref-for-concept-body-body%E2%91%A8
JS::GCPtr<Infrastructure::Body const> Response::body_impl() const
{
// Objects including the Body interface mixin have an associated body (null or a body).
// A Response objects body is its responses body.
return m_response->body() ? m_response->body() : nullptr;
}
// https://fetch.spec.whatwg.org/#concept-body-body
// https://fetch.spec.whatwg.org/#ref-for-concept-body-body%E2%91%A8
JS::GCPtr<Infrastructure::Body> Response::body_impl()
{
// Objects including the Body interface mixin have an associated body (null or a body).
// A Response objects body is its responses body.
return m_response->body() ? m_response->body() : nullptr;
}
// https://fetch.spec.whatwg.org/#response-create
JS::NonnullGCPtr<Response> Response::create(JS::Realm& realm, JS::NonnullGCPtr<Infrastructure::Response> response, Headers::Guard guard)
{
// 1. Let responseObject be a new Response object with realm.
// 2. Set responseObjects response to response.
auto response_object = realm.heap().allocate<Response>(realm, realm, response);
// 3. Set responseObjects headers to a new Headers object with realm, whose headers list is responses headers list and guard is guard.
response_object->m_headers = realm.heap().allocate<Headers>(realm, realm, response->header_list());
response_object->m_headers->set_guard(guard);
// 4. Return responseObject.
return response_object;
}
// https://fetch.spec.whatwg.org/#initialize-a-response
WebIDL::ExceptionOr<void> Response::initialize_response(ResponseInit const& init, Optional<Infrastructure::BodyWithType> const& body)
{
// 1. If init["status"] is not in the range 200 to 599, inclusive, then throw a RangeError.
if (init.status < 200 || init.status > 599)
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Status must be in range 200-599"sv };
// FIXME: 2. If init["statusText"] does not match the reason-phrase token production, then throw a TypeError.
// 3. Set responses responses status to init["status"].
m_response->set_status(init.status);
// 4. Set responses responses status message to init["statusText"].
m_response->set_status_message(MUST(ByteBuffer::copy(init.status_text.bytes())));
// 5. If init["headers"] exists, then fill responses headers with init["headers"].
if (init.headers.has_value())
TRY(m_headers->fill(*init.headers));
// 6. If body was given, then:
if (body.has_value()) {
// 1. If responses status is a null body status, then throw a TypeError.
if (Infrastructure::is_null_body_status(m_response->status()))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Response with null body status cannot have a body"sv };
// 2. Set responses body to bodys body.
m_response->set_body(body->body);
// 3. If bodys type is non-null and responses header list does not contain `Content-Type`, then append (`Content-Type`, bodys type) to responses 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));
}
}
return {};
}
// https://fetch.spec.whatwg.org/#dom-response
WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::construct_impl(JS::Realm& realm, Optional<BodyInit> const& body, ResponseInit const& init)
{
auto& vm = realm.vm();
// Referred to as 'this' in the spec.
auto response_object = realm.heap().allocate<Response>(realm, realm, Infrastructure::Response::create(vm));
// 1. Set thiss response to a new response.
// NOTE: This is done at the beginning as the 'this' value Response object
// cannot exist with a null Infrastructure::Response.
// 2. Set thiss headers to a new Headers object with thiss relevant Realm, whose header list is thiss responses header list and guard is "response".
response_object->m_headers = realm.heap().allocate<Headers>(realm, realm, response_object->response()->header_list());
response_object->m_headers->set_guard(Headers::Guard::Response);
// 3. Let bodyWithType be null.
Optional<Infrastructure::BodyWithType> body_with_type;
// 4. If body is non-null, then set bodyWithType to the result of extracting body.
if (body.has_value())
body_with_type = TRY(extract_body(realm, *body));
// 5. Perform initialize a response given this, init, and bodyWithType.
TRY(response_object->initialize_response(init, body_with_type));
return response_object;
}
// https://fetch.spec.whatwg.org/#dom-response-error
JS::NonnullGCPtr<Response> Response::error(JS::VM& vm)
{
// The static error() method steps are to return the result of creating a Response object, given a new network error, "immutable", and thiss relevant Realm.
// FIXME: How can we reliably get 'this', i.e. the object the function was called on, in IDL-defined functions?
return Response::create(*vm.current_realm(), Infrastructure::Response::network_error(vm, "Response created via `Response.error()`"sv), Headers::Guard::Immutable);
}
// https://fetch.spec.whatwg.org/#dom-response-redirect
WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::redirect(JS::VM& vm, String const& url, u16 status)
{
auto& realm = *vm.current_realm();
// 1. Let parsedURL be the result of parsing url with current settings objects API base URL.
auto api_base_url = HTML::current_settings_object().api_base_url();
auto parsed_url = DOMURL::parse(url, api_base_url);
// 2. If parsedURL is failure, then throw a TypeError.
if (!parsed_url.is_valid())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Redirect URL is not valid"sv };
// 3. If status is not a redirect status, then throw a RangeError.
if (!Infrastructure::is_redirect_status(status))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::RangeError, "Status must be one of 301, 302, 303, 307, or 308"sv };
// 4. Let responseObject be the result of creating a Response object, given a new response, "immutable", and thiss relevant Realm.
// FIXME: How can we reliably get 'this', i.e. the object the function was called on, in IDL-defined functions?
auto response_object = Response::create(realm, Infrastructure::Response::create(vm), Headers::Guard::Immutable);
// 5. Set responseObjects responses status to status.
response_object->response()->set_status(status);
// 6. Let value be parsedURL, serialized and isomorphic encoded.
auto value = parsed_url.serialize();
// 7. Append (`Location`, value) to responseObjects responses header list.
auto header = Infrastructure::Header::from_string_pair("Location"sv, value);
response_object->response()->header_list()->append(move(header));
// 8. Return responseObject.
return response_object;
}
// https://fetch.spec.whatwg.org/#dom-response-json
WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::json(JS::VM& vm, JS::Value data, ResponseInit const& init)
{
auto& realm = *vm.current_realm();
// 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data.
auto bytes = TRY(Infra::serialize_javascript_value_to_json_bytes(vm, data));
// 2. Let body be the result of extracting bytes.
auto [body, _] = TRY(extract_body(realm, { bytes.bytes() }));
// 3. Let responseObject be the result of creating a Response object, given a new response, "response", and thiss relevant Realm.
// FIXME: How can we reliably get 'this', i.e. the object the function was called on, in IDL-defined functions?
auto response_object = Response::create(realm, Infrastructure::Response::create(vm), Headers::Guard::Response);
// 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()))
};
TRY(response_object->initialize_response(init, move(body_with_type)));
// 5. Return responseObject.
return response_object;
}
// https://fetch.spec.whatwg.org/#dom-response-type
Bindings::ResponseType Response::type() const
{
// The type getter steps are to return thiss responses type.
return to_bindings_enum(m_response->type());
}
// https://fetch.spec.whatwg.org/#dom-response-url
String Response::url() const
{
// The url getter steps are to return the empty string if thiss responses URL is null; otherwise thiss responses URL, serialized with exclude fragment set to true.
return !m_response->url().has_value()
? String {}
: MUST(String::from_byte_string(m_response->url()->serialize(URL::ExcludeFragment::Yes)));
}
// https://fetch.spec.whatwg.org/#dom-response-redirected
bool Response::redirected() const
{
// The redirected getter steps are to return true if thiss responses URL list has more than one item; otherwise false.
return m_response->url_list().size() > 1;
}
// https://fetch.spec.whatwg.org/#dom-response-status
u16 Response::status() const
{
// The status getter steps are to return thiss responses status.
return m_response->status();
}
// https://fetch.spec.whatwg.org/#dom-response-ok
bool Response::ok() const
{
// The ok getter steps are to return true if thiss responses status is an ok status; otherwise false.
return Infrastructure::is_ok_status(m_response->status());
}
// https://fetch.spec.whatwg.org/#dom-response-statustext
String Response::status_text() const
{
// The statusText getter steps are to return thiss responses status message.
return MUST(String::from_utf8(m_response->status_message()));
}
// https://fetch.spec.whatwg.org/#dom-response-headers
JS::NonnullGCPtr<Headers> Response::headers() const
{
// The headers getter steps are to return thiss headers.
return *m_headers;
}
// https://fetch.spec.whatwg.org/#dom-response-clone
WebIDL::ExceptionOr<JS::NonnullGCPtr<Response>> Response::clone() const
{
auto& realm = this->realm();
// 1. If this is unusable, then throw a TypeError.
if (is_unusable())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Response is unusable"sv };
// 2. Let clonedResponse be the result of cloning thiss response.
auto cloned_response = m_response->clone(realm);
// 3. Return the result of creating a Response object, given clonedResponse, thiss headerss guard, and thiss relevant Realm.
return Response::create(HTML::relevant_realm(*this), cloned_response, m_headers->guard());
}
}