ladybird/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Responses.h
Timothy Flynn 9375660b64 LibHTTP+LibWeb+RequestServer: Move Fetch's HTTP header infra to LibHTTP
The end goal here is for LibHTTP to be the home of our RFC 9111 (HTTP
caching) implementation. We currently have one implementation in LibWeb
for our in-memory cache and another in RequestServer for our disk cache.

The implementations both largely revolve around interacting with HTTP
headers. But in LibWeb, we are using Fetch's header infra, and in RS we
are using are home-grown header infra from LibHTTP.

So to give these a common denominator, this patch replaces the LibHTTP
implementation with Fetch's infra. Our existing LibHTTP implementation
was not particularly compliant with any spec, so this at least gives us
a standards-based common implementation.

This migration also required moving a handful of other Fetch AOs over
to LibHTTP. (It turns out these AOs were all from the Fetch/Infra/HTTP
folder, so perhaps it makes sense for LibHTTP to be the implementation
of that entire set of facilities.)
2025-11-27 14:57:29 +01:00

349 lines
16 KiB
C++

/*
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.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 <LibHTTP/HeaderList.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/Cell.h>
#include <LibURL/URL.h>
#include <LibWeb/Export.h>
#include <LibWeb/Fetch/Infrastructure/HTTP.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
namespace Web::Fetch::Infrastructure {
// https://fetch.spec.whatwg.org/#concept-response
class WEB_API 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&, String 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 ByteString const& status_message() const { return m_status_message; }
virtual void set_status_message(ByteString status_message) { m_status_message = move(status_message); }
virtual NonnullRefPtr<HTTP::HeaderList> const& header_list() const { return m_header_list; }
virtual void set_header_list(NonnullRefPtr<HTTP::HeaderList> header_list) { m_header_list = move(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<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; }
[[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 = move(body_info); }
[[nodiscard]] RedirectTaint redirect_taint() const { return m_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;
[[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_same_origin() const;
[[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<String> const& network_error_message() const { return m_network_error_message; }
MonotonicTime response_time() const { return m_response_time; }
protected:
explicit Response(NonnullRefPtr<HTTP::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.
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.
NonnullRefPtr<HTTP::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<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.
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-redirect-taint
// A response has an associated redirect taint ("same-origin", "same-site", or "cross-site"), which is initially "same-origin".
RedirectTaint m_redirect_taint { RedirectTaint::SameOrigin };
// FIXME: is the type correct?
u64 current_age() const;
u64 freshness_lifetime() const;
u64 stale_while_revalidate_lifetime() const;
// Non-standard
ByteString m_method;
MonotonicTime m_response_time;
Optional<String> m_network_error_message;
public:
[[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
class FilteredResponse : public Response {
GC_CELL(FilteredResponse, Response);
public:
FilteredResponse(GC::Ref<Response>, NonnullRefPtr<HTTP::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 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)); }
virtual NonnullRefPtr<HTTP::HeaderList> const& header_list() const override { return m_internal_response->header_list(); }
virtual void set_header_list(NonnullRefPtr<HTTP::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<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); }
[[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 WEB_API 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; }
virtual NonnullRefPtr<HTTP::HeaderList> const& header_list() const override { return m_header_list; }
private:
BasicFilteredResponse(GC::Ref<Response>, NonnullRefPtr<HTTP::HeaderList>);
NonnullRefPtr<HTTP::HeaderList> m_header_list;
};
// https://fetch.spec.whatwg.org/#concept-filtered-response-cors
class WEB_API 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; }
virtual NonnullRefPtr<HTTP::HeaderList> const& header_list() const override { return m_header_list; }
private:
CORSFilteredResponse(GC::Ref<Response>, NonnullRefPtr<HTTP::HeaderList>);
NonnullRefPtr<HTTP::HeaderList> m_header_list;
};
// https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
class WEB_API 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 ByteString const& status_message() const override { return m_method; }
virtual NonnullRefPtr<HTTP::HeaderList> const& header_list() const override { return m_header_list; }
[[nodiscard]] virtual GC::Ptr<Body> body() const override { return nullptr; }
private:
OpaqueFilteredResponse(GC::Ref<Response>, NonnullRefPtr<HTTP::HeaderList>);
Vector<URL::URL> m_url_list;
ByteString const m_method;
NonnullRefPtr<HTTP::HeaderList> m_header_list;
};
// https://fetch.spec.whatwg.org/#concept-filtered-response-opaque-redirect
class WEB_API 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 ByteString const& status_message() const override { return m_method; }
virtual NonnullRefPtr<HTTP::HeaderList> const& header_list() const override { return m_header_list; }
[[nodiscard]] virtual GC::Ptr<Body> body() const override { return nullptr; }
private:
OpaqueRedirectFilteredResponse(GC::Ref<Response>, NonnullRefPtr<HTTP::HeaderList>);
ByteString const m_method;
NonnullRefPtr<HTTP::HeaderList> m_header_list;
};
}