ladybird/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h
Timothy Flynn 9396a643b8 LibWeb: Ensure FilteredResponse setters forward to the base class
The spec for filtered responses states:

    Unless stated otherwise a filtered response’s associated concepts
    (such as its body) refer to the associated concepts of its internal
    response.

This includes setting its associated concepts. In particular, when the
filtered response's body is set upon fetching a request with integrity
metadata, we must set the internal response's body instead.

Further restrictions that apply to filtered response subclasses (such as
opaque filtered responses having a status code of 0) are already
implemented.
2024-12-09 20:02:51 -07:00

350 lines
16 KiB
C++

/*
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteBuffer.h>
#include <AK/Error.h>
#include <AK/Forward.h>
#include <AK/Optional.h>
#include <AK/Time.h>
#include <AK/Vector.h>
#include <LibGC/Ptr.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibURL/URL.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
namespace Web::Fetch::Infrastructure {
// https://fetch.spec.whatwg.org/#concept-response
class Response : public JS::Cell {
GC_CELL(Response, JS::Cell);
GC_DECLARE_ALLOCATOR(Response);
public:
enum class CacheState {
Local,
Validated,
};
enum class Type {
Basic,
CORS,
Default,
Error,
Opaque,
OpaqueRedirect,
};
// https://fetch.spec.whatwg.org/#response-body-info
struct BodyInfo {
// https://fetch.spec.whatwg.org/#fetch-timing-info-encoded-body-size
u64 encoded_size { 0 };
// https://fetch.spec.whatwg.org/#fetch-timing-info-decoded-body-size
u64 decoded_size { 0 };
// https://fetch.spec.whatwg.org/#response-body-info-content-type
String content_type {};
bool operator==(BodyInfo const&) const = default;
};
[[nodiscard]] static GC::Ref<Response> create(JS::VM&);
[[nodiscard]] static GC::Ref<Response> aborted_network_error(JS::VM&);
[[nodiscard]] static GC::Ref<Response> network_error(JS::VM&, Variant<String, StringView> message);
[[nodiscard]] static GC::Ref<Response> appropriate_network_error(JS::VM&, FetchParams const&);
virtual ~Response() = default;
[[nodiscard]] virtual Type type() const { return m_type; }
void set_type(Type type) { m_type = type; }
[[nodiscard]] virtual bool aborted() const { return m_aborted; }
virtual void set_aborted(bool aborted) { m_aborted = aborted; }
[[nodiscard]] virtual Vector<URL::URL> const& url_list() const { return m_url_list; }
[[nodiscard]] virtual Vector<URL::URL>& url_list() { return m_url_list; }
virtual void set_url_list(Vector<URL::URL> url_list) { m_url_list = move(url_list); }
[[nodiscard]] virtual Status status() const { return m_status; }
virtual void set_status(Status status) { m_status = status; }
[[nodiscard]] virtual ReadonlyBytes status_message() const { return m_status_message; }
virtual void set_status_message(ByteBuffer 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; }
[[nodiscard]] virtual GC::Ptr<Body> body() const { return m_body; }
virtual void set_body(GC::Ptr<Body> body) { m_body = body; }
[[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 bool range_requested() const { return m_range_requested; }
virtual void set_range_requested(bool range_requested) { m_range_requested = range_requested; }
[[nodiscard]] virtual bool request_includes_credentials() const { return m_request_includes_credentials; }
virtual void set_request_includes_credentials(bool request_includes_credentials) { m_request_includes_credentials = request_includes_credentials; }
[[nodiscard]] virtual bool timing_allow_passed() const { return m_timing_allow_passed; }
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; }
[[nodiscard]] bool has_cross_origin_redirects() const { return m_has_cross_origin_redirects; }
void set_has_cross_origin_redirects(bool has_cross_origin_redirects) { m_has_cross_origin_redirects = has_cross_origin_redirects; }
[[nodiscard]] bool is_aborted_network_error() const;
[[nodiscard]] bool is_network_error() const;
[[nodiscard]] Optional<URL::URL const&> url() const;
[[nodiscard]] ErrorOr<Optional<URL::URL>> location_url(Optional<String> const& request_fragment) const;
[[nodiscard]] GC::Ref<Response> clone(JS::Realm&) const;
[[nodiscard]] GC::Ref<Response> unsafe_response();
[[nodiscard]] bool is_cors_cross_origin() const;
[[nodiscard]] bool is_fresh() const;
[[nodiscard]] bool is_stale_while_revalidate() const;
[[nodiscard]] bool is_stale() const;
// Non-standard
[[nodiscard]] Optional<StringView> network_error_message() const;
protected:
explicit Response(GC::Ref<HeaderList>);
virtual void visit_edges(JS::Cell::Visitor&) override;
private:
// https://fetch.spec.whatwg.org/#concept-response-type
// A response has an associated type which is "basic", "cors", "default", "error", "opaque", or "opaqueredirect". Unless stated otherwise, it is "default".
Type m_type { Type::Default };
// https://fetch.spec.whatwg.org/#concept-response-aborted
// A response can have an associated aborted flag, which is initially unset.
bool m_aborted { false };
// https://fetch.spec.whatwg.org/#concept-response-url-list
// A response has an associated URL list (a list of zero or more URLs). Unless stated otherwise, it is the empty list.
Vector<URL::URL> m_url_list;
// https://fetch.spec.whatwg.org/#concept-response-status
// A response has an associated status, which is a status. Unless stated otherwise it is 200.
Status m_status { 200 };
// 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;
// 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.
GC::Ref<HeaderList> m_header_list;
// https://fetch.spec.whatwg.org/#concept-response-body
// A response has an associated body (null or a body). Unless stated otherwise it is null.
GC::Ptr<Body> m_body;
// https://fetch.spec.whatwg.org/#concept-response-cache-state
// A response has an associated cache state (the empty string, "local", or "validated"). Unless stated otherwise, it is the empty string.
Optional<CacheState> m_cache_state;
// 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;
// https://fetch.spec.whatwg.org/#concept-response-range-requested-flag
// A response has an associated range-requested flag, which is initially unset.
bool m_range_requested { false };
// https://fetch.spec.whatwg.org/#response-request-includes-credentials
// A response has an associated request-includes-credentials (a boolean), which is initially true.
bool m_request_includes_credentials { true };
// https://fetch.spec.whatwg.org/#concept-response-timing-allow-passed
// A response has an associated timing allow passed flag, which is initially unset.
bool m_timing_allow_passed { false };
// https://fetch.spec.whatwg.org/#concept-response-body-info
// A response has an associated body info (a response body info). Unless stated otherwise, it is a new response body info.
BodyInfo m_body_info;
// https://fetch.spec.whatwg.org/#response-service-worker-timing-info
// FIXME: A response has an associated service worker timing info (null or a service worker timing info), which is initially null.
// https://fetch.spec.whatwg.org/#response-has-cross-origin-redirects
// A response has an associated has-cross-origin-redirects (a boolean), which is initially false.
bool m_has_cross_origin_redirects { false };
// FIXME is the type correct?
u64 current_age() const;
u64 freshness_lifetime() const;
u64 stale_while_revalidate_lifetime() const;
// Non-standard
ByteBuffer m_method;
UnixDateTime m_response_time;
Optional<Variant<String, StringView>> m_network_error_message;
public:
[[nodiscard]] ByteBuffer const& method() const { return m_method; }
void set_method(ByteBuffer method) { m_method = move(method); }
};
// https://fetch.spec.whatwg.org/#concept-filtered-response
class FilteredResponse : public Response {
GC_CELL(FilteredResponse, Response);
public:
FilteredResponse(GC::Ref<Response>, GC::Ref<HeaderList>);
virtual ~FilteredResponse() = 0;
[[nodiscard]] virtual Type type() const override { return m_internal_response->type(); }
[[nodiscard]] virtual bool aborted() const override { return m_internal_response->aborted(); }
virtual void set_aborted(bool aborted) override { m_internal_response->set_aborted(aborted); }
[[nodiscard]] virtual Vector<URL::URL> const& url_list() const override { return m_internal_response->url_list(); }
[[nodiscard]] virtual Vector<URL::URL>& url_list() override { return m_internal_response->url_list(); }
virtual void set_url_list(Vector<URL::URL> url_list) override { m_internal_response->set_url_list(move(url_list)); }
[[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 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 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); }
[[nodiscard]] virtual GC::Ptr<Body> body() const override { return m_internal_response->body(); }
virtual void set_body(GC::Ptr<Body> body) override { m_internal_response->set_body(body); }
[[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 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); }
[[nodiscard]] virtual bool request_includes_credentials() const override { return m_internal_response->request_includes_credentials(); }
virtual void set_request_includes_credentials(bool request_includes_credentials) override { m_internal_response->set_request_includes_credentials(request_includes_credentials); }
[[nodiscard]] virtual bool timing_allow_passed() const override { return m_internal_response->timing_allow_passed(); }
virtual void set_timing_allow_passed(bool timing_allow_passed) override { m_internal_response->set_timing_allow_passed(timing_allow_passed); }
[[nodiscard]] virtual BodyInfo const& body_info() const override { return m_internal_response->body_info(); }
virtual void set_body_info(BodyInfo body_info) override { m_internal_response->set_body_info(move(body_info)); }
[[nodiscard]] GC::Ref<Response> internal_response() const { return m_internal_response; }
protected:
virtual void visit_edges(JS::Cell::Visitor&) override;
private:
// https://fetch.spec.whatwg.org/#concept-internal-response
GC::Ref<Response> m_internal_response;
};
// https://fetch.spec.whatwg.org/#concept-filtered-response-basic
class BasicFilteredResponse final : public FilteredResponse {
GC_CELL(BasicFilteredResponse, FilteredResponse);
GC_DECLARE_ALLOCATOR(BasicFilteredResponse);
public:
[[nodiscard]] static GC::Ref<BasicFilteredResponse> create(JS::VM&, GC::Ref<Response>);
[[nodiscard]] virtual Type type() const override { return Type::Basic; }
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const override { return m_header_list; }
private:
BasicFilteredResponse(GC::Ref<Response>, GC::Ref<HeaderList>);
virtual void visit_edges(JS::Cell::Visitor&) override;
GC::Ref<HeaderList> m_header_list;
};
// https://fetch.spec.whatwg.org/#concept-filtered-response-cors
class CORSFilteredResponse final : public FilteredResponse {
GC_CELL(CORSFilteredResponse, FilteredResponse);
GC_DECLARE_ALLOCATOR(CORSFilteredResponse);
public:
[[nodiscard]] static GC::Ref<CORSFilteredResponse> create(JS::VM&, GC::Ref<Response>);
[[nodiscard]] virtual Type type() const override { return Type::CORS; }
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const override { return m_header_list; }
private:
CORSFilteredResponse(GC::Ref<Response>, GC::Ref<HeaderList>);
virtual void visit_edges(JS::Cell::Visitor&) override;
GC::Ref<HeaderList> m_header_list;
};
// https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
class OpaqueFilteredResponse final : public FilteredResponse {
GC_CELL(OpaqueFilteredResponse, FilteredResponse);
GC_DECLARE_ALLOCATOR(OpaqueFilteredResponse);
public:
[[nodiscard]] static GC::Ref<OpaqueFilteredResponse> create(JS::VM&, GC::Ref<Response>);
[[nodiscard]] virtual Type type() const override { return Type::Opaque; }
[[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 override { return {}; }
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const override { return m_header_list; }
[[nodiscard]] virtual GC::Ptr<Body> body() const override { return nullptr; }
private:
OpaqueFilteredResponse(GC::Ref<Response>, GC::Ref<HeaderList>);
virtual void visit_edges(JS::Cell::Visitor&) override;
Vector<URL::URL> m_url_list;
GC::Ref<HeaderList> m_header_list;
};
// https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect
class OpaqueRedirectFilteredResponse final : public FilteredResponse {
GC_CELL(OpaqueRedirectFilteredResponse, FilteredResponse);
GC_DECLARE_ALLOCATOR(OpaqueRedirectFilteredResponse);
public:
[[nodiscard]] static GC::Ref<OpaqueRedirectFilteredResponse> create(JS::VM&, GC::Ref<Response>);
[[nodiscard]] virtual Type type() const override { return Type::OpaqueRedirect; }
[[nodiscard]] virtual Status status() const override { return 0; }
[[nodiscard]] virtual ReadonlyBytes status_message() const override { return {}; }
[[nodiscard]] virtual GC::Ref<HeaderList> header_list() const override { return m_header_list; }
[[nodiscard]] virtual GC::Ptr<Body> body() const override { return nullptr; }
private:
OpaqueRedirectFilteredResponse(GC::Ref<Response>, GC::Ref<HeaderList>);
virtual void visit_edges(JS::Cell::Visitor&) override;
GC::Ref<HeaderList> m_header_list;
};
}