2022-07-16 17:55:29 +01:00
/*
2023-03-02 23:26:35 +00:00
* Copyright ( c ) 2022 - 2023 , Linus Groh < linusg @ serenityos . org >
2022-07-16 17:55:29 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-10-23 22:16:14 +01:00
# include <AK/Debug.h>
2022-11-23 13:28:01 +01:00
# include <AK/TypeCasts.h>
2024-11-15 04:01:23 +13:00
# include <LibGC/Heap.h>
2025-11-28 10:39:53 -05:00
# include <LibHTTP/Cache/Utilities.h>
2023-01-07 12:14:54 -05:00
# include <LibJS/Runtime/Completion.h>
2022-10-30 01:52:07 +00:00
# include <LibJS/Runtime/VM.h>
2022-10-24 18:46:27 +01:00
# include <LibWeb/Bindings/MainThreadVM.h>
2024-02-11 19:48:56 +13:00
# include <LibWeb/DOMURL/DOMURL.h>
2022-10-23 22:50:38 +01:00
# include <LibWeb/Fetch/Infrastructure/FetchParams.h>
2022-09-25 19:23:35 +01:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
2025-11-26 13:32:39 -05:00
# include <LibWeb/Fetch/Infrastructure/HTTP/CORS.h>
2022-07-16 17:55:29 +01:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
2025-11-26 14:13:23 -05:00
# include <LibWeb/MimeSniff/MimeType.h>
2022-07-16 17:55:29 +01:00
2022-07-17 23:52:02 +01:00
namespace Web : : Fetch : : Infrastructure {
2022-07-16 17:55:29 +01:00
2024-11-15 04:01:23 +13:00
GC_DEFINE_ALLOCATOR ( Response ) ;
GC_DEFINE_ALLOCATOR ( BasicFilteredResponse ) ;
GC_DEFINE_ALLOCATOR ( CORSFilteredResponse ) ;
GC_DEFINE_ALLOCATOR ( OpaqueFilteredResponse ) ;
GC_DEFINE_ALLOCATOR ( OpaqueRedirectFilteredResponse ) ;
2023-11-19 19:47:52 +01:00
2025-11-26 14:13:23 -05:00
GC : : Ref < Response > Response : : create ( JS : : VM & vm )
{
return vm . heap ( ) . allocate < Response > ( HTTP : : HeaderList : : create ( ) ) ;
}
Response : : Response ( NonnullRefPtr < HTTP : : HeaderList > header_list )
: m_header_list ( move ( header_list ) )
2025-11-28 10:39:53 -05:00
, m_response_time ( UnixDateTime : : now ( ) )
, m_monotonic_response_time ( MonotonicTime : : now ( ) )
2022-09-25 20:52:51 +01:00
{
}
2022-10-30 01:52:07 +00:00
void Response : : visit_edges ( JS : : Cell : : Visitor & visitor )
2022-10-04 23:45:47 +01:00
{
2022-10-30 01:52:07 +00:00
Base : : visit_edges ( visitor ) ;
2023-08-18 19:38:13 +02:00
visitor . visit ( m_body ) ;
2022-10-30 01:52:07 +00:00
}
2022-07-16 17:55:29 +01:00
// https://fetch.spec.whatwg.org/#ref-for-concept-network-error%E2%91%A3
// A network error is a response whose status is always 0, status message is always
// the empty byte sequence, header list is always empty, and body is always null.
2024-11-15 04:01:23 +13:00
GC : : Ref < Response > Response : : aborted_network_error ( JS : : VM & vm )
2022-07-16 17:55:29 +01:00
{
2025-04-02 20:51:45 +13:00
auto response = network_error ( vm , " Fetch has been aborted " _string ) ;
2022-09-24 00:28:11 +01:00
response - > set_aborted ( true ) ;
2022-07-16 17:55:29 +01:00
return response ;
}
2025-04-02 20:51:45 +13:00
GC : : Ref < Response > Response : : network_error ( JS : : VM & vm , String message )
2022-07-16 17:55:29 +01:00
{
2025-04-02 20:51:45 +13:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Creating network error response with message: {} " , message ) ;
2022-10-30 01:52:07 +00:00
auto response = Response : : create ( vm ) ;
2022-09-24 00:28:11 +01:00
response - > set_status ( 0 ) ;
response - > set_type ( Type : : Error ) ;
2023-08-18 19:38:13 +02:00
VERIFY ( ! response - > body ( ) ) ;
2022-10-23 19:10:17 +02:00
response - > m_network_error_message = move ( message ) ;
2022-07-16 17:55:29 +01:00
return response ;
}
2022-10-23 22:50:38 +01:00
// https://fetch.spec.whatwg.org/#appropriate-network-error
2024-11-15 04:01:23 +13:00
GC : : Ref < Response > Response : : appropriate_network_error ( JS : : VM & vm , FetchParams const & fetch_params )
2022-10-23 22:50:38 +01:00
{
// 1. Assert: fetchParams is canceled.
VERIFY ( fetch_params . is_canceled ( ) ) ;
// 2. Return an aborted network error if fetchParams is aborted; otherwise return a network error.
return fetch_params . is_aborted ( )
2023-03-03 18:02:43 +00:00
? aborted_network_error ( vm )
2025-04-02 20:51:45 +13:00
: network_error ( vm , " Fetch has been terminated " _string ) ;
2022-10-23 22:50:38 +01:00
}
2022-07-16 17:55:29 +01:00
// https://fetch.spec.whatwg.org/#concept-aborted-network-error
bool Response : : is_aborted_network_error ( ) const
{
// A response whose type is "error" and aborted flag is set is known as an aborted network error.
2022-10-24 18:51:25 +01:00
// NOTE: We have to use the virtual getter here to not bypass filtered responses.
return type ( ) = = Type : : Error & & aborted ( ) ;
2022-07-16 17:55:29 +01:00
}
// https://fetch.spec.whatwg.org/#concept-network-error
bool Response : : is_network_error ( ) const
{
2023-10-02 19:30:44 +02:00
// A network error is a response whose type is "error", status is 0, status message is the empty byte sequence,
// header list is « », body is null, and body info is a new response body info.
2022-10-24 18:51:25 +01:00
// NOTE: We have to use the virtual getter here to not bypass filtered responses.
2023-10-02 19:30:44 +02:00
if ( type ( ) ! = Type : : Error )
return false ;
if ( status ( ) ! = 0 )
return false ;
if ( ! status_message ( ) . is_empty ( ) )
return false ;
if ( ! header_list ( ) - > is_empty ( ) )
return false ;
2024-01-18 16:34:16 -05:00
if ( body ( ) )
2023-10-02 19:30:44 +02:00
return false ;
if ( body_info ( ) ! = BodyInfo { } )
return false ;
return true ;
2022-07-16 17:55:29 +01:00
}
// https://fetch.spec.whatwg.org/#concept-response-url
2024-03-18 16:22:27 +13:00
Optional < URL : : URL const & > Response : : url ( ) const
2022-07-16 17:55:29 +01:00
{
// A response has an associated URL. It is a pointer to the last URL in response’ s URL list and null if response’ s URL list is empty.
2022-10-24 18:51:25 +01:00
// NOTE: We have to use the virtual getter here to not bypass filtered responses.
if ( url_list ( ) . is_empty ( ) )
2022-07-16 17:55:29 +01:00
return { } ;
2022-10-24 18:51:25 +01:00
return url_list ( ) . last ( ) ;
2022-07-16 17:55:29 +01:00
}
// https://fetch.spec.whatwg.org/#concept-response-location-url
2024-03-18 16:22:27 +13:00
ErrorOr < Optional < URL : : URL > > Response : : location_url ( Optional < String > const & request_fragment ) const
2022-07-16 17:55:29 +01:00
{
// The location URL of a response response, given null or an ASCII string requestFragment, is the value returned by the following steps. They return null, failure, or a URL.
// 1. If response’ s status is not a redirect status, then return null.
2022-10-24 18:51:25 +01:00
// NOTE: We have to use the virtual getter here to not bypass filtered responses.
if ( ! is_redirect_status ( status ( ) ) )
2024-03-18 16:22:27 +13:00
return Optional < URL : : URL > { } ;
2022-07-16 17:55:29 +01:00
2022-10-25 23:03:39 +01:00
// 2. Let location be the result of extracting header list values given `Location` and response’ s header list.
2025-11-24 18:35:55 -05:00
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 > > ( ) ;
2023-02-08 23:32:44 +00:00
2025-11-25 11:09:28 -05:00
if ( ! location_values | | location_values - > size ( ) ! = 1 )
return OptionalNone { } ;
2022-07-16 17:55:29 +01:00
// 3. If location is a header value, then set location to the result of parsing location with response’ s URL.
2025-11-25 11:09:28 -05:00
auto location = DOMURL : : parse ( location_values - > first ( ) , url ( ) ) ;
2025-01-22 17:35:52 +13:00
if ( ! location . has_value ( ) )
2024-09-05 15:06:15 +04:00
return Error : : from_string_literal ( " Invalid 'Location' header URL " ) ;
2022-07-16 17:55:29 +01:00
// 4. If location is a URL whose fragment is null, then set location’ s fragment to requestFragment.
2025-01-22 17:35:52 +13:00
if ( ! location - > fragment ( ) . has_value ( ) )
location - > set_fragment ( request_fragment ) ;
2022-07-16 17:55:29 +01:00
// 5. Return location.
return location ;
}
2022-09-25 19:23:35 +01:00
// https://fetch.spec.whatwg.org/#concept-response-clone
2024-11-15 04:01:23 +13:00
GC : : Ref < Response > Response : : clone ( JS : : Realm & realm ) const
2022-09-25 19:23:35 +01:00
{
// To clone a response response, run these steps:
2023-02-28 17:45:49 +00:00
auto & vm = realm . vm ( ) ;
2022-09-25 19:23:35 +01:00
2022-10-24 18:46:27 +01:00
// 1. If response is a filtered response, then return a new identical filtered response whose internal response is a clone of response’ s internal response.
if ( is < FilteredResponse > ( * this ) ) {
2024-04-26 13:42:39 -04:00
auto internal_response = static_cast < FilteredResponse const & > ( * this ) . internal_response ( ) - > clone ( realm ) ;
2022-10-24 18:46:27 +01:00
if ( is < BasicFilteredResponse > ( * this ) )
2024-04-26 13:42:39 -04:00
return BasicFilteredResponse : : create ( vm , internal_response ) ;
2022-10-24 18:46:27 +01:00
if ( is < CORSFilteredResponse > ( * this ) )
2024-04-26 13:42:39 -04:00
return CORSFilteredResponse : : create ( vm , internal_response ) ;
2022-10-24 18:46:27 +01:00
if ( is < OpaqueFilteredResponse > ( * this ) )
2022-10-30 01:52:07 +00:00
return OpaqueFilteredResponse : : create ( vm , internal_response ) ;
2022-10-24 18:46:27 +01:00
if ( is < OpaqueRedirectFilteredResponse > ( * this ) )
2022-10-30 01:52:07 +00:00
return OpaqueRedirectFilteredResponse : : create ( vm , internal_response ) ;
2022-10-24 18:46:27 +01:00
VERIFY_NOT_REACHED ( ) ;
}
2022-09-25 19:23:35 +01:00
// 2. Let newResponse be a copy of response, except for its body.
2022-10-30 01:52:07 +00:00
auto new_response = Infrastructure : : Response : : create ( vm ) ;
2022-10-04 23:45:47 +01:00
new_response - > set_type ( m_type ) ;
new_response - > set_aborted ( m_aborted ) ;
new_response - > set_url_list ( m_url_list ) ;
new_response - > set_status ( m_status ) ;
new_response - > set_status_message ( m_status_message ) ;
for ( auto const & header : * m_header_list )
2024-04-26 13:24:20 -04:00
new_response - > header_list ( ) - > append ( header ) ;
2022-10-04 23:45:47 +01:00
new_response - > set_cache_state ( m_cache_state ) ;
new_response - > set_cors_exposed_header_name_list ( m_cors_exposed_header_name_list ) ;
new_response - > set_range_requested ( m_range_requested ) ;
new_response - > set_request_includes_credentials ( m_request_includes_credentials ) ;
new_response - > set_timing_allow_passed ( m_timing_allow_passed ) ;
new_response - > set_body_info ( m_body_info ) ;
// FIXME: service worker timing info
2022-09-25 19:25:53 +01:00
// 3. If response’ s body is non-null, then set newResponse’ s body to the result of cloning response’ s body.
2023-08-18 19:38:13 +02:00
if ( m_body )
2023-08-13 13:05:26 +02:00
new_response - > set_body ( m_body - > clone ( realm ) ) ;
2022-09-25 19:23:35 +01:00
// 4. Return newResponse.
return new_response ;
}
2023-05-10 16:57:23 -04:00
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#unsafe-response
2024-11-15 04:01:23 +13:00
GC : : Ref < Response > Response : : unsafe_response ( )
2023-05-10 16:57:23 -04:00
{
// A response's unsafe response is its internal response if it has one, and the response itself otherwise.
if ( is < FilteredResponse > ( this ) )
return static_cast < FilteredResponse & > ( * this ) . internal_response ( ) ;
return * this ;
}
2024-12-20 16:51:31 +00:00
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-same-origin
bool Response : : is_cors_same_origin ( ) const
{
// A response whose type is "basic", "cors", or "default" is CORS-same-origin. [FETCH]
switch ( type ( ) ) {
case Type : : Basic :
case Type : : CORS :
case Type : : Default :
return true ;
default :
return false ;
}
}
2023-05-10 16:27:55 -04:00
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-cross-origin
bool Response : : is_cors_cross_origin ( ) const
{
// A response whose type is "opaque" or "opaqueredirect" is CORS-cross-origin.
return type ( ) = = Type : : Opaque | | type ( ) = = Type : : OpaqueRedirect ;
}
2023-03-23 22:16:15 -04:00
// https://fetch.spec.whatwg.org/#concept-fresh-response
bool Response : : is_fresh ( ) const
{
// A fresh response is a response whose current age is within its freshness lifetime.
return current_age ( ) < freshness_lifetime ( ) ;
}
// https://fetch.spec.whatwg.org/#concept-stale-while-revalidate-response
bool Response : : is_stale_while_revalidate ( ) const
{
// A stale-while-revalidate response is a response that is not a fresh response and whose current age is within the stale-while-revalidate lifetime.
return ! is_fresh ( ) & & current_age ( ) < stale_while_revalidate_lifetime ( ) ;
}
// https://fetch.spec.whatwg.org/#concept-stale-response
bool Response : : is_stale ( ) const
{
// A stale response is a response that is not a fresh response or a stale-while-revalidate response.
return ! is_fresh ( ) & & ! is_stale_while_revalidate ( ) ;
}
2025-11-28 10:39:53 -05:00
AK : : Duration Response : : current_age ( ) const
2023-03-23 22:16:15 -04:00
{
// FIXME: Let's get the correct time.
2025-11-28 10:39:53 -05:00
auto const request_time = UnixDateTime : : now ( ) - AK : : Duration : : from_seconds ( 5 ) ;
2023-03-23 22:16:15 -04:00
2025-11-28 10:39:53 -05:00
return HTTP : : calculate_age ( m_header_list , request_time , m_response_time ) ;
2023-03-23 22:16:15 -04:00
}
2025-11-28 10:39:53 -05:00
AK : : Duration Response : : freshness_lifetime ( ) const
2023-03-23 22:16:15 -04:00
{
2025-11-28 10:39:53 -05:00
return HTTP : : calculate_freshness_lifetime ( m_status , m_header_list ) ;
2023-03-23 22:16:15 -04:00
}
// https://httpwg.org/specs/rfc5861.html#n-the-stale-while-revalidate-cache-control-extension
2025-11-28 10:39:53 -05:00
AK : : Duration Response : : stale_while_revalidate_lifetime ( ) const
2023-03-23 22:16:15 -04:00
{
2025-11-24 18:35:55 -05:00
auto const elem = header_list ( ) - > get_decode_and_split ( " Cache-Control " sv ) ;
2023-03-23 22:16:15 -04:00
if ( ! elem . has_value ( ) )
2025-11-28 10:39:53 -05:00
return { } ;
2023-03-23 22:16:15 -04:00
for ( auto const & directive : * elem ) {
if ( directive . starts_with_bytes ( " stale-while-revalidate " sv ) ) {
2024-06-22 18:28:40 +02:00
auto equal_offset = directive . find_byte_offset ( ' = ' ) ;
if ( ! equal_offset . has_value ( ) ) {
dbgln ( " Bogus directive: '{}' " , directive ) ;
continue ;
}
auto const value_string = directive . bytes_as_string_view ( ) . substring_view ( equal_offset . value ( ) + 1 ) ;
2025-11-28 10:39:53 -05:00
auto maybe_value = value_string . to_number < i64 > ( ) ;
2024-06-22 18:28:40 +02:00
if ( ! maybe_value . has_value ( ) ) {
dbgln ( " Bogus directive: '{}' " , directive ) ;
continue ;
}
2025-11-28 10:39:53 -05:00
return AK : : Duration : : from_seconds ( * maybe_value ) ;
2023-03-23 22:16:15 -04:00
}
}
2025-11-28 10:39:53 -05:00
return { } ;
2023-03-23 22:16:15 -04:00
}
2023-03-03 18:02:43 +00:00
// Non-standard
2025-11-26 14:13:23 -05:00
FilteredResponse : : FilteredResponse ( GC : : Ref < Response > internal_response , NonnullRefPtr < HTTP : : HeaderList > header_list )
: Response ( move ( header_list ) )
2022-10-30 01:52:07 +00:00
, m_internal_response ( internal_response )
2022-07-16 17:55:29 +01:00
{
}
FilteredResponse : : ~ FilteredResponse ( )
{
}
2022-10-30 01:52:07 +00:00
void FilteredResponse : : visit_edges ( JS : : Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_internal_response ) ;
}
2024-11-15 04:01:23 +13:00
GC : : Ref < BasicFilteredResponse > BasicFilteredResponse : : create ( JS : : VM & vm , GC : : Ref < Response > internal_response )
2022-07-16 17:55:29 +01:00
{
// A basic filtered response is a filtered response whose type is "basic" and header list excludes
// any headers in internal response’ s header list whose name is a forbidden response-header name.
2025-11-26 14:13:23 -05:00
auto header_list = HTTP : : HeaderList : : create ( ) ;
2022-10-04 23:45:47 +01:00
for ( auto const & header : * internal_response - > header_list ( ) ) {
2025-11-26 14:13:23 -05:00
if ( ! HTTP : : is_forbidden_response_header_name ( header . name ) )
2024-04-26 13:24:20 -04:00
header_list - > append ( header ) ;
2022-07-16 17:55:29 +01:00
}
2025-11-26 14:13:23 -05:00
return vm . heap ( ) . allocate < BasicFilteredResponse > ( internal_response , move ( header_list ) ) ;
2022-07-16 17:55:29 +01:00
}
2025-11-26 14:13:23 -05:00
BasicFilteredResponse : : BasicFilteredResponse ( GC : : Ref < Response > internal_response , NonnullRefPtr < HTTP : : HeaderList > header_list )
2022-10-30 01:52:07 +00:00
: FilteredResponse ( internal_response , header_list )
2025-11-26 14:13:23 -05:00
, m_header_list ( move ( header_list ) )
2022-10-30 01:52:07 +00:00
{
}
2024-11-15 04:01:23 +13:00
GC : : Ref < CORSFilteredResponse > CORSFilteredResponse : : create ( JS : : VM & vm , GC : : Ref < Response > internal_response )
2022-07-16 17:55:29 +01:00
{
// 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.
2025-11-24 18:35:55 -05:00
Vector < StringView > cors_exposed_header_name_list ;
cors_exposed_header_name_list . ensure_capacity ( internal_response - > cors_exposed_header_name_list ( ) . size ( ) ) ;
2022-10-04 23:45:47 +01:00
for ( auto const & header_name : internal_response - > cors_exposed_header_name_list ( ) )
2025-11-24 18:35:55 -05:00
cors_exposed_header_name_list . unchecked_append ( header_name ) ;
2022-07-16 17:55:29 +01:00
2025-11-26 14:13:23 -05:00
auto header_list = HTTP : : HeaderList : : create ( ) ;
2022-10-04 23:45:47 +01:00
for ( auto const & header : * internal_response - > header_list ( ) ) {
2022-07-16 17:55:29 +01:00
if ( is_cors_safelisted_response_header_name ( header . name , cors_exposed_header_name_list ) )
2024-04-26 13:24:20 -04:00
header_list - > append ( header ) ;
2022-07-16 17:55:29 +01:00
}
2024-11-14 06:13:46 +13:00
return vm . heap ( ) . allocate < CORSFilteredResponse > ( internal_response , header_list ) ;
2022-07-16 17:55:29 +01:00
}
2025-11-26 14:13:23 -05:00
CORSFilteredResponse : : CORSFilteredResponse ( GC : : Ref < Response > internal_response , NonnullRefPtr < HTTP : : HeaderList > header_list )
2022-10-30 01:52:07 +00:00
: FilteredResponse ( internal_response , header_list )
2025-11-26 14:13:23 -05:00
, m_header_list ( move ( header_list ) )
2022-07-16 17:55:29 +01:00
{
}
2024-11-15 04:01:23 +13:00
GC : : Ref < OpaqueFilteredResponse > OpaqueFilteredResponse : : create ( JS : : VM & vm , GC : : Ref < Response > internal_response )
2022-10-30 01:52:07 +00:00
{
// An opaque filtered response is a filtered response whose type is "opaque", URL list is the empty list,
2022-07-16 17:55:29 +01:00
// status is 0, status message is the empty byte sequence, header list is empty, and body is null.
2025-11-26 14:13:23 -05:00
return vm . heap ( ) . allocate < OpaqueFilteredResponse > ( internal_response , HTTP : : HeaderList : : create ( ) ) ;
2022-10-30 01:52:07 +00:00
}
2025-11-26 14:13:23 -05:00
OpaqueFilteredResponse : : OpaqueFilteredResponse ( GC : : Ref < Response > internal_response , NonnullRefPtr < HTTP : : HeaderList > header_list )
2022-10-30 01:52:07 +00:00
: FilteredResponse ( internal_response , header_list )
2025-11-26 14:13:23 -05:00
, m_header_list ( move ( header_list ) )
2022-07-16 17:55:29 +01:00
{
2022-10-30 01:52:07 +00:00
}
2024-11-15 04:01:23 +13:00
GC : : Ref < OpaqueRedirectFilteredResponse > OpaqueRedirectFilteredResponse : : create ( JS : : VM & vm , GC : : Ref < Response > internal_response )
2022-10-30 01:52:07 +00:00
{
// An opaque-redirect filtered response is a filtered response whose type is "opaqueredirect",
// status is 0, status message is the empty byte sequence, header list is empty, and body is null.
2025-11-26 14:13:23 -05:00
return vm . heap ( ) . allocate < OpaqueRedirectFilteredResponse > ( internal_response , HTTP : : HeaderList : : create ( ) ) ;
2022-07-16 17:55:29 +01:00
}
2025-11-26 14:13:23 -05:00
OpaqueRedirectFilteredResponse : : OpaqueRedirectFilteredResponse ( GC : : Ref < Response > internal_response , NonnullRefPtr < HTTP : : HeaderList > header_list )
2022-10-30 01:52:07 +00:00
: FilteredResponse ( internal_response , header_list )
2025-11-26 14:13:23 -05:00
, m_header_list ( move ( header_list ) )
2022-07-16 17:55:29 +01:00
{
}
}