2022-10-23 22:16:14 +01:00
/*
2023-03-02 23:26:35 +00:00
* Copyright ( c ) 2022 - 2023 , Linus Groh < linusg @ serenityos . org >
2023-02-08 23:35:52 +00:00
* Copyright ( c ) 2023 , Luke Wilde < lukew @ serenityos . org >
2023-04-20 16:52:01 +01:00
* Copyright ( c ) 2023 , Sam Atkins < atkinssj @ serenityos . org >
2024-04-29 20:54:14 +01:00
* Copyright ( c ) 2024 , Jamie Mansfield < jmansfield @ cadixdev . org >
2025-01-19 19:02:18 +13:00
* Copyright ( c ) 2025 , Shannon Booth < shannon @ serenityos . org >
2025-08-06 21:27:17 +02:00
* Copyright ( c ) 2025 , Kenneth Myhra < kennethmyhra @ serenityos . org >
2022-10-23 22:16:14 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/Base64.h>
# include <AK/Debug.h>
# include <AK/ScopeGuard.h>
2023-01-07 12:14:54 -05:00
# include <LibJS/Runtime/Completion.h>
2025-02-26 13:28:21 +00:00
# include <LibRequests/RequestTimingInfo.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Bindings/MainThreadVM.h>
2024-11-08 04:03:55 +13:00
# include <LibWeb/Bindings/PrincipalHostDefined.h>
2024-11-25 17:29:27 +00:00
# include <LibWeb/ContentSecurityPolicy/BlockingAlgorithms.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Cookie/Cookie.h>
# include <LibWeb/DOM/Document.h>
2024-02-11 19:48:56 +13:00
# include <LibWeb/DOMURL/DOMURL.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Fetch/BodyInit.h>
# include <LibWeb/Fetch/Fetching/Checks.h>
2024-05-26 08:03:29 -04:00
# include <LibWeb/Fetch/Fetching/FetchedDataReceiver.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Fetch/Fetching/Fetching.h>
# include <LibWeb/Fetch/Fetching/PendingResponse.h>
# include <LibWeb/Fetch/Fetching/RefCountedFlag.h>
# include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
# include <LibWeb/Fetch/Infrastructure/FetchController.h>
# include <LibWeb/Fetch/Infrastructure/FetchParams.h>
2024-07-18 21:31:42 +01:00
# include <LibWeb/Fetch/Infrastructure/FetchRecord.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Fetch/Infrastructure/FetchTimingInfo.h>
2024-05-19 13:23:38 +01:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Fetch/Infrastructure/HTTP/Methods.h>
# include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
# include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
# include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
# include <LibWeb/Fetch/Infrastructure/MimeTypeBlocking.h>
2024-05-31 18:44:49 +01:00
# include <LibWeb/Fetch/Infrastructure/NetworkPartitionKey.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Fetch/Infrastructure/NoSniffBlocking.h>
# include <LibWeb/Fetch/Infrastructure/PortBlocking.h>
# include <LibWeb/Fetch/Infrastructure/Task.h>
# include <LibWeb/Fetch/Infrastructure/URL.h>
2023-08-01 18:53:39 -04:00
# include <LibWeb/FileAPI/Blob.h>
# include <LibWeb/FileAPI/BlobURLStore.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/HTML/EventLoop/EventLoop.h>
# include <LibWeb/HTML/Scripting/Environments.h>
2024-04-27 10:00:47 +02:00
# include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/HTML/Window.h>
2023-11-09 18:09:21 -07:00
# include <LibWeb/HTML/WorkerGlobalScope.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/HighResolutionTime/TimeOrigin.h>
# include <LibWeb/Loader/LoadRequest.h>
# include <LibWeb/Loader/ResourceLoader.h>
2024-05-29 19:01:06 +01:00
# include <LibWeb/MixedContent/AbstractOperations.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/Platform/EventLoopPlugin.h>
# include <LibWeb/ReferrerPolicy/AbstractOperations.h>
2025-02-26 15:51:05 +00:00
# include <LibWeb/ResourceTiming/PerformanceResourceTiming.h>
2023-04-20 16:52:01 +01:00
# include <LibWeb/SRI/SRI.h>
2024-04-29 20:54:14 +01:00
# include <LibWeb/SecureContexts/AbstractOperations.h>
2024-04-27 10:00:47 +02:00
# include <LibWeb/Streams/TransformStream.h>
# include <LibWeb/Streams/TransformStreamDefaultController.h>
2025-04-17 20:02:23 -04:00
# include <LibWeb/Streams/TransformStreamOperations.h>
2024-04-27 10:00:47 +02:00
# include <LibWeb/Streams/Transformer.h>
2022-10-23 22:16:14 +01:00
# include <LibWeb/WebIDL/DOMException.h>
namespace Web : : Fetch : : Fetching {
2025-06-28 03:32:38 -07:00
bool g_http_cache_enabled = false ;
2024-06-22 18:53:11 +02:00
2023-01-14 16:34:44 -07:00
# define TRY_OR_IGNORE(expression) \
( { \
2023-02-09 13:27:43 -05:00
auto & & _temporary_result = ( expression ) ; \
2023-01-14 16:34:44 -07:00
if ( _temporary_result . is_error ( ) ) \
return ; \
static_assert ( ! : : AK : : Detail : : IsLvalueReference < decltype ( _temporary_result . release_value ( ) ) > , \
" Do not return a reference from a fallible expression " ) ; \
_temporary_result . release_value ( ) ; \
2022-10-23 22:16:14 +01:00
} )
// https://fetch.spec.whatwg.org/#concept-fetch
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < Infrastructure : : FetchController > > fetch ( JS : : Realm & realm , Infrastructure : : Request & request , Infrastructure : : FetchAlgorithms const & algorithms , UseParallelQueue use_parallel_queue )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'fetch' with: request @ {} " , & request ) ;
auto & vm = realm . vm ( ) ;
// 1. Assert: request’ s mode is "navigate" or processEarlyHintsResponse is null.
2023-08-18 18:26:08 +02:00
VERIFY ( request . mode ( ) = = Infrastructure : : Request : : Mode : : Navigate | | ! algorithms . process_early_hints_response ( ) ) ;
2022-10-23 22:16:14 +01:00
// 2. Let taskDestination be null.
2025-07-16 12:29:26 +02:00
Infrastructure : : TaskDestination task_destination ;
2022-10-23 22:16:14 +01:00
// 3. Let crossOriginIsolatedCapability be false.
auto cross_origin_isolated_capability = HTML : : CanUseCrossOriginIsolatedAPIs : : No ;
2025-08-07 11:41:58 +02:00
// 4. Populate request from client given request.
populate_request_from_client ( realm , request ) ;
// 5. If request’ s client is non-null, then:
2022-10-23 22:16:14 +01:00
if ( request . client ( ) ! = nullptr ) {
// 1. Set taskDestination to request’ s client’ s global object.
2025-07-16 12:29:26 +02:00
task_destination = GC : : Ref { request . client ( ) - > global_object ( ) } ;
2022-10-23 22:16:14 +01:00
// 2. Set crossOriginIsolatedCapability to request’ s client’ s cross-origin isolated capability.
cross_origin_isolated_capability = request . client ( ) - > cross_origin_isolated_capability ( ) ;
}
2025-08-07 11:41:58 +02:00
// 6. If useParallelQueue is true, then set taskDestination to the result of starting a new parallel queue.
2025-07-16 12:29:26 +02:00
if ( use_parallel_queue = = UseParallelQueue : : Yes )
task_destination = HTML : : ParallelQueue : : create ( ) ;
2022-10-23 22:16:14 +01:00
2025-08-07 11:41:58 +02:00
// 7. Let timingInfo be a new fetch timing info whose start time and post-redirect start time are the coarsened
2022-10-23 22:16:14 +01:00
// shared current time given crossOriginIsolatedCapability, and render-blocking is set to request’ s
// render-blocking.
auto timing_info = Infrastructure : : FetchTimingInfo : : create ( vm ) ;
auto now = HighResolutionTime : : coarsened_shared_current_time ( cross_origin_isolated_capability = = HTML : : CanUseCrossOriginIsolatedAPIs : : Yes ) ;
timing_info - > set_start_time ( now ) ;
timing_info - > set_post_redirect_start_time ( now ) ;
timing_info - > set_render_blocking ( request . render_blocking ( ) ) ;
2025-08-07 11:41:58 +02:00
// 8. Let fetchParams be a new fetch params whose request is request, timing info is timingInfo, process request
2022-10-23 22:16:14 +01:00
// body chunk length is processRequestBodyChunkLength, process request end-of-body is processRequestEndOfBody,
// process early hints response is processEarlyHintsResponse, process response is processResponse, process
// response consume body is processResponseConsumeBody, process response end-of-body is processResponseEndOfBody,
// task destination is taskDestination, and cross-origin isolated capability is crossOriginIsolatedCapability.
auto fetch_params = Infrastructure : : FetchParams : : create ( vm , request , timing_info ) ;
2023-02-25 10:44:51 -07:00
fetch_params - > set_algorithms ( algorithms ) ;
2025-07-16 12:29:26 +02:00
fetch_params - > set_task_destination ( task_destination ) ;
2022-10-23 22:16:14 +01:00
fetch_params - > set_cross_origin_isolated_capability ( cross_origin_isolated_capability ) ;
2025-08-07 11:41:58 +02:00
// 9. If request’ s body is a byte sequence, then set request’ s body to request’ s body as a body.
2022-10-23 22:16:14 +01:00
if ( auto const * buffer = request . body ( ) . get_pointer < ByteBuffer > ( ) )
2024-11-04 16:06:01 +01:00
request . set_body ( Infrastructure : : byte_sequence_as_body ( realm , buffer - > bytes ( ) ) ) ;
2022-10-23 22:16:14 +01:00
2025-08-07 11:41:58 +02:00
// 10. If all of the following conditions are true:
2024-07-12 19:35:06 +01:00
if (
// - request’ s URL’ s scheme is an HTTP(S) scheme
Infrastructure : : is_http_or_https_scheme ( request . url ( ) . scheme ( ) )
// - request’ s mode is "same-origin", "cors", or "no-cors"
& & ( request . mode ( ) = = Infrastructure : : Request : : Mode : : SameOrigin | | request . mode ( ) = = Infrastructure : : Request : : Mode : : CORS | | request . mode ( ) = = Infrastructure : : Request : : Mode : : NoCORS )
2025-08-07 11:41:58 +02:00
// - 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 ( ) )
2024-07-12 19:35:06 +01:00
// - request’ s method is `GET`
& & StringView { 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.
2024-10-05 15:33:34 +13:00
VERIFY ( request . origin ( ) . has < URL : : Origin > ( ) & & request . origin ( ) . get < URL : : Origin > ( ) . is_same_origin ( request . client ( ) - > origin ( ) ) ) ;
2024-07-12 19:35:06 +01:00
// 2. Let onPreloadedResponseAvailable be an algorithm that runs the following step given a response
// response: set fetchParams’ s preloaded response candidate to response.
2024-11-15 04:01:23 +13:00
auto on_preloaded_response_available = GC : : create_function ( realm . heap ( ) , [ fetch_params ] ( GC : : Ref < Infrastructure : : Response > response ) {
2024-07-12 19:35:06 +01:00
fetch_params - > set_preloaded_response_candidate ( response ) ;
} ) ;
// FIXME: 3. Let foundPreloadedResource be the result of invoking consume a preloaded resource for request’ s
// window, given request’ s URL, request’ s destination, request’ s mode, request’ s credentials mode,
// request’ s integrity metadata, and onPreloadedResponseAvailable.
auto found_preloaded_resource = false ;
( void ) on_preloaded_response_available ;
// 4. If foundPreloadedResource is true and fetchParams’ s preloaded response candidate is null, then set
// fetchParams’ s preloaded response candidate to "pending".
if ( found_preloaded_resource & & fetch_params - > preloaded_response_candidate ( ) . has < Empty > ( ) )
fetch_params - > set_preloaded_response_candidate ( Infrastructure : : FetchParams : : PreloadedResponseCandidatePendingTag { } ) ;
}
2025-08-07 11:41:58 +02:00
// 11. If request’ s header list does not contain `Accept`, then:
2022-10-23 22:16:14 +01:00
if ( ! request . header_list ( ) - > contains ( " Accept " sv . bytes ( ) ) ) {
// 1. Let value be `*/*`.
auto value = " */* " sv ;
2024-07-12 20:01:10 +01:00
// 2. If request’ s initiator is "prefetch", then set value to the document `Accept` header value.
if ( request . initiator ( ) = = Infrastructure : : Request : : Initiator : : Prefetch ) {
value = document_accept_header_value ;
}
// 3. Otherwise, the user agent should set value to the first matching statement, if any, switching on request’ s destination:
else if ( request . destination ( ) . has_value ( ) ) {
2022-10-23 22:16:14 +01:00
switch ( * request . destination ( ) ) {
// -> "document"
// -> "frame"
// -> "iframe"
case Infrastructure : : Request : : Destination : : Document :
case Infrastructure : : Request : : Destination : : Frame :
case Infrastructure : : Request : : Destination : : IFrame :
2024-07-12 20:01:10 +01:00
// the document `Accept` header value
value = document_accept_header_value ;
2022-10-23 22:16:14 +01:00
break ;
// -> "image"
case Infrastructure : : Request : : Destination : : Image :
// `image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5`
value = " image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5 " sv ;
break ;
2024-05-03 20:44:06 +01:00
// -> "json"
case Infrastructure : : Request : : Destination : : JSON :
// `application/json,*/*;q=0.5`
value = " application/json,*/*;q=0.5 " sv ;
break ;
2022-10-23 22:16:14 +01:00
// -> "style"
case Infrastructure : : Request : : Destination : : Style :
// `text/css,*/*;q=0.1`
value = " text/css,*/*;q=0.1 " sv ;
break ;
default :
break ;
}
}
2024-07-12 20:01:10 +01:00
// 4. Append (`Accept`, value) to request’ s header list.
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Accept " sv , value . bytes ( ) ) ;
request . header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
2025-08-07 11:41:58 +02:00
// 12. If request’ s header list does not contain `Accept-Language`, then user agents should append
2022-10-23 22:16:14 +01:00
// (`Accept-Language, an appropriate header value) to request’ s header list.
if ( ! request . header_list ( ) - > contains ( " Accept-Language " sv . bytes ( ) ) ) {
2024-07-23 21:10:24 +01:00
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 ( ) ) ;
2024-04-26 13:24:20 -04:00
request . header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
2025-08-07 11:41:58 +02:00
// 13. If request’ s internal priority is null, then use request’ s priority, initiator, destination, and
// render-blocking in an implementation-defined manner to set request’ s internal priority to an
// implementation-defined object.
2022-10-23 22:16:14 +01:00
// NOTE: The user-agent-defined object could encompass stream weight and dependency for HTTP/2, and equivalent
// information used to prioritize dispatch and processing of HTTP/1 fetches.
2025-08-07 11:41:58 +02:00
// 14. If request is a subresource request, then:
2022-10-23 22:16:14 +01:00
if ( request . is_subresource_request ( ) ) {
2024-07-22 20:46:39 +02:00
// 1. Let record be a new fetch record whose request is request and controller is fetchParams’ s controller.
auto record = Infrastructure : : FetchRecord : : create ( vm , request , fetch_params - > controller ( ) ) ;
2025-08-07 11:41:58 +02:00
// 2. Append record to request’ s client’ s fetch group’ s fetch records.
2024-07-22 20:46:39 +02:00
request . client ( ) - > fetch_group ( ) . append ( record ) ;
2022-10-23 22:16:14 +01:00
}
2025-08-07 11:41:58 +02:00
// 15. Run main fetch given fetchParams.
2022-10-23 22:16:14 +01:00
( void ) TRY ( main_fetch ( realm , fetch_params ) ) ;
2025-08-07 11:41:58 +02:00
// 16. Return fetchParams’ s controller.
2022-10-23 22:16:14 +01:00
return fetch_params - > controller ( ) ;
}
2025-08-06 21:27:17 +02:00
// https://fetch.spec.whatwg.org/#populate-request-from-client
void populate_request_from_client ( JS : : Realm const & realm , Infrastructure : : Request & request )
{
auto & heap = realm . heap ( ) ;
// 1. If request’ s traversable for user prompts is "client":
auto const * traversable_for_user_prompts = request . traversable_for_user_prompts ( ) . get_pointer < Infrastructure : : Request : : TraversableForUserPrompts > ( ) ;
if ( traversable_for_user_prompts & & * traversable_for_user_prompts = = Infrastructure : : Request : : TraversableForUserPrompts : : Client ) {
// 1. Set request’ s traversable for user prompts to "no-traversable".
request . set_traversable_for_user_prompts ( Infrastructure : : Request : : TraversableForUserPrompts : : NoTraversable ) ;
// 2. If request’ s client is non-null:
if ( request . client ( ) ) {
// 1. Let global be request’ s client’ s global object.
auto & global = request . client ( ) - > global_object ( ) ;
// 2. If global is a Window object and global’ s navigable is not null, then set request’ s traversable for
// user prompts to global’ s navigable’ s traversable navigable.
if ( auto const * window = as_if < HTML : : Window > ( global ) ) {
if ( window - > navigable ( ) )
request . set_traversable_for_user_prompts ( window - > navigable ( ) - > traversable_navigable ( ) ) ;
}
}
}
// 2. If request’ s origin is "client":
auto const * origin = request . origin ( ) . get_pointer < Infrastructure : : Request : : Origin > ( ) ;
if ( origin & & * origin = = Infrastructure : : Request : : Origin : : Client ) {
// 1. Assert: request’ s client is non-null.
VERIFY ( request . client ( ) ) ;
// 2. Set request’ s origin to request’ s client’ s origin.
request . set_origin ( request . client ( ) - > origin ( ) ) ;
}
// 3. If request’ s policy container is "client":
auto const * policy_container = request . policy_container ( ) . get_pointer < Infrastructure : : Request : : PolicyContainer > ( ) ;
if ( policy_container & & * policy_container = = Infrastructure : : Request : : PolicyContainer : : Client ) {
// 1. If request’ s client is non-null, then set request’ s policy container to a clone of request’ s client’ s
// policy container.
if ( request . client ( ) )
request . set_policy_container ( request . client ( ) - > policy_container ( ) - > clone ( heap ) ) ;
// 2. Otherwise, set request’ s policy container to a new policy container.
else
request . set_policy_container ( heap . allocate < HTML : : PolicyContainer > ( heap ) ) ;
}
}
2022-10-23 22:16:14 +01:00
// https://fetch.spec.whatwg.org/#concept-main-fetch
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ptr < PendingResponse > > main_fetch ( JS : : Realm & realm , Infrastructure : : FetchParams const & fetch_params , Recursive recursive )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'main fetch' with: fetch_params @ {} " , & fetch_params ) ;
auto & vm = realm . vm ( ) ;
// 1. Let request be fetchParams’ s request.
auto request = fetch_params . request ( ) ;
// 2. Let response be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Infrastructure : : Response > response ;
2022-10-23 22:16:14 +01:00
// 3. If request’ s local-URLs-only flag is set and request’ s current URL is not local, then set response to a
// network error.
if ( request - > local_urls_only ( ) & & ! Infrastructure : : is_local_url ( request - > current_url ( ) ) )
2025-04-02 20:51:45 +13:00
response = Infrastructure : : Response : : network_error ( vm , " Request with 'local-URLs-only' flag must have a local URL " _string ) ;
2022-10-23 22:16:14 +01:00
2024-11-28 11:57:04 +00:00
// 4. Run report Content Security Policy violations for request.
ContentSecurityPolicy : : report_content_security_policy_violations_for_request ( realm , request ) ;
2022-10-23 22:16:14 +01:00
// FIXME: 5. Upgrade request to a potentially trustworthy URL, if appropriate.
2024-05-29 19:27:27 +01:00
// 6. Upgrade a mixed content request to a potentially trustworthy URL, if appropriate.
MixedContent : : upgrade_a_mixed_content_request_to_a_potentially_trustworthy_url_if_appropriate ( request ) ;
2022-10-23 22:16:14 +01:00
2025-08-07 22:41:54 +02:00
// 7. If should request be blocked due to a bad port, should fetching request be blocked as mixed content, should
// request be blocked by Content Security Policy, or should request be blocked by Integrity Policy Policy
// returns blocked, then set response to a network error.
2022-10-23 22:16:14 +01:00
if ( Infrastructure : : block_bad_port ( request ) = = Infrastructure : : RequestOrResponseBlocking : : Blocked
2024-05-29 19:01:06 +01:00
| | MixedContent : : should_fetching_request_be_blocked_as_mixed_content ( request ) = = Infrastructure : : RequestOrResponseBlocking : : Blocked
2025-08-07 22:41:54 +02:00
| | ContentSecurityPolicy : : should_request_be_blocked_by_content_security_policy ( realm , request ) = = ContentSecurityPolicy : : Directives : : Directive : : Result : : Blocked
| | ContentSecurityPolicy : : should_request_be_blocked_by_integrity_policy ( request ) = = ContentSecurityPolicy : : Directives : : Directive : : Result : : Blocked ) {
2025-04-02 20:51:45 +13:00
response = Infrastructure : : Response : : network_error ( vm , " Request was blocked " _string ) ;
2022-10-23 22:16:14 +01:00
}
2023-04-19 15:49:15 +01:00
// 8. If request’ s referrer policy is the empty string, then set request’ s referrer policy to request’ s policy
2022-10-23 22:16:14 +01:00
// container’ s referrer policy.
2024-03-05 09:35:25 -07:00
if ( request - > referrer_policy ( ) = = ReferrerPolicy : : ReferrerPolicy : : EmptyString ) {
2024-11-25 14:30:12 +00:00
VERIFY ( request - > policy_container ( ) . has < GC : : Ref < HTML : : PolicyContainer > > ( ) ) ;
request - > set_referrer_policy ( request - > policy_container ( ) . get < GC : : Ref < HTML : : PolicyContainer > > ( ) - > referrer_policy ) ;
2022-10-23 22:16:14 +01:00
}
2023-04-19 15:49:15 +01:00
// 9. If request’ s referrer is not "no-referrer", then set request’ s referrer to the result of invoking determine
2022-10-23 22:16:14 +01:00
// request’ s referrer.
// NOTE: As stated in Referrer Policy, user agents can provide the end user with options to override request’ s
// referrer to "no-referrer" or have it expose less sensitive information.
auto const * referrer = request - > referrer ( ) . get_pointer < Infrastructure : : Request : : Referrer > ( ) ;
if ( ! referrer | | * referrer ! = Infrastructure : : Request : : Referrer : : NoReferrer ) {
auto determined_referrer = ReferrerPolicy : : determine_requests_referrer ( request ) ;
if ( determined_referrer . has_value ( ) )
request - > set_referrer ( * determined_referrer ) ;
else
request - > set_referrer ( Infrastructure : : Request : : Referrer : : NoReferrer ) ;
}
2023-04-19 15:49:15 +01:00
// 10. Set request’ s current URL’ s scheme to "https" if all of the following conditions are true:
2022-10-23 22:16:14 +01:00
if (
// - request’ s current URL’ s scheme is "http"
request - > current_url ( ) . scheme ( ) = = " http " sv
// - request’ s current URL’ s host is a domain
2024-11-27 15:12:17 +00:00
& & request - > current_url ( ) . host ( ) . has_value ( ) & & request - > current_url ( ) . host ( ) - > is_domain ( )
2022-10-23 22:16:14 +01:00
// FIXME: - Matching request’ s current URL’ s host per Known HSTS Host Domain Name Matching results in either a
// superdomain match with an asserted includeSubDomains directive or a congruent match (with or without an
// asserted includeSubDomains directive) [HSTS]; or DNS resolution for the request finds a matching HTTPS RR
// per section 9.5 of [SVCB].
2025-04-23 11:07:04 -04:00
& & false ) {
2023-08-12 16:52:41 +12:00
request - > current_url ( ) . set_scheme ( " https " _string ) ;
2022-10-23 22:16:14 +01:00
}
2024-11-15 04:01:23 +13:00
auto get_response = GC : : create_function ( vm . heap ( ) , [ & realm , & vm , & fetch_params , request ] ( ) - > WebIDL : : ExceptionOr < GC : : Ref < PendingResponse > > {
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'main fetch' get_response() function " ) ;
2025-04-23 11:14:15 -04:00
auto const * origin = request - > origin ( ) . get_pointer < URL : : Origin > ( ) ;
2022-10-23 22:16:14 +01:00
// -> fetchParams’ s preloaded response candidate is not null
if ( ! fetch_params . preloaded_response_candidate ( ) . has < Empty > ( ) ) {
// 1. Wait until fetchParams’ s preloaded response candidate is not "pending".
2024-11-15 04:01:23 +13:00
HTML : : main_thread_event_loop ( ) . spin_until ( GC : : create_function ( vm . heap ( ) , [ & ] {
2022-10-23 22:16:14 +01:00
return ! fetch_params . preloaded_response_candidate ( ) . has < Infrastructure : : FetchParams : : PreloadedResponseCandidatePendingTag > ( ) ;
2024-10-31 05:23:43 +13:00
} ) ) ;
2022-10-23 22:16:14 +01:00
// 2. Assert: fetchParams’ s preloaded response candidate is a response.
2024-11-15 04:01:23 +13:00
VERIFY ( fetch_params . preloaded_response_candidate ( ) . has < GC : : Ref < Infrastructure : : Response > > ( ) ) ;
2022-10-23 22:16:14 +01:00
// 3. Return fetchParams’ s preloaded response candidate.
2024-11-15 04:01:23 +13:00
return PendingResponse : : create ( vm , request , fetch_params . preloaded_response_candidate ( ) . get < GC : : Ref < Infrastructure : : Response > > ( ) ) ;
2022-10-23 22:16:14 +01:00
}
2025-04-23 11:07:04 -04:00
// -> request’ s current URL’ s origin is same origin with request’ s origin, and request’ s response tainting is "basic"
2022-10-23 22:16:14 +01:00
// -> request’ s current URL’ s scheme is "data"
// -> request’ s mode is "navigate" or "websocket"
2025-04-23 11:07:04 -04:00
if (
2025-04-23 11:14:15 -04:00
( origin & & request - > current_url ( ) . origin ( ) . is_same_origin ( * origin ) & & request - > response_tainting ( ) = = Infrastructure : : Request : : ResponseTainting : : Basic )
2022-10-23 22:16:14 +01:00
| | request - > current_url ( ) . scheme ( ) = = " data " sv
| | ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : Navigate | | request - > mode ( ) = = Infrastructure : : Request : : Mode : : WebSocket ) ) {
// 1. Set request’ s response tainting to "basic".
request - > set_response_tainting ( Infrastructure : : Request : : ResponseTainting : : Basic ) ;
// 2. Return the result of running scheme fetch given fetchParams.
return scheme_fetch ( realm , fetch_params ) ;
// NOTE: HTML assigns any documents and workers created from URLs whose scheme is "data" a unique
// opaque origin. Service workers can only be created from URLs whose scheme is an HTTP(S) scheme.
}
2025-04-23 11:07:04 -04:00
2022-10-23 22:16:14 +01:00
// -> request’ s mode is "same-origin"
2025-04-23 11:07:04 -04:00
if ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : SameOrigin ) {
2022-10-23 22:16:14 +01:00
// Return a network error.
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request with 'same-origin' mode must have same URL and request origin " _string ) ) ;
2022-10-23 22:16:14 +01:00
}
2025-04-23 11:07:04 -04:00
2022-10-23 22:16:14 +01:00
// -> request’ s mode is "no-cors"
2025-04-23 11:07:04 -04:00
if ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : NoCORS ) {
2022-10-23 22:16:14 +01:00
// 1. If request’ s redirect mode is not "follow", then return a network error.
if ( request - > redirect_mode ( ) ! = Infrastructure : : Request : : RedirectMode : : Follow )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request with 'no-cors' mode must have redirect mode set to 'follow' " _string ) ) ;
2022-10-23 22:16:14 +01:00
// 2. Set request’ s response tainting to "opaque".
request - > set_response_tainting ( Infrastructure : : Request : : ResponseTainting : : Opaque ) ;
// 3. Return the result of running scheme fetch given fetchParams.
return scheme_fetch ( realm , fetch_params ) ;
}
2025-04-23 11:07:04 -04:00
2022-10-23 22:16:14 +01:00
// -> request’ s current URL’ s scheme is not an HTTP(S) scheme
2025-04-23 11:14:15 -04:00
// AD-HOC: We allow CORS requests for resource:// URLs from opaque origins to enable requesting JS modules from internal pages.
if ( ! Infrastructure : : is_http_or_https_scheme ( request - > current_url ( ) . scheme ( ) )
& & ! ( origin & & origin - > is_opaque ( ) & & request - > current_url ( ) . scheme ( ) = = " resource " sv ) ) {
2022-10-23 22:16:14 +01:00
// NOTE: At this point all other request modes have been handled. Ensure we're not lying in the error message :^)
VERIFY ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : CORS ) ;
// Return a network error.
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request with 'cors' mode must have URL with HTTP or HTTPS scheme " _string ) ) ;
2022-10-23 22:16:14 +01:00
}
2025-04-23 11:07:04 -04:00
2022-10-23 22:16:14 +01:00
// -> request’ s use-CORS-preflight flag is set
// -> request’ s unsafe-request flag is set and either request’ s method is not a CORS-safelisted method or
// CORS-unsafe request-header names with request’ s header list is not empty
2025-04-23 11:07:04 -04:00
if (
2022-10-23 22:16:14 +01:00
request - > use_cors_preflight ( )
| | ( request - > unsafe_request ( )
& & ( ! Infrastructure : : is_cors_safelisted_method ( request - > method ( ) )
2024-04-26 13:24:20 -04:00
| | ! Infrastructure : : get_cors_unsafe_header_names ( request - > header_list ( ) ) . is_empty ( ) ) ) ) {
2022-10-23 22:16:14 +01:00
// 1. Set request’ s response tainting to "cors".
request - > set_response_tainting ( Infrastructure : : Request : : ResponseTainting : : CORS ) ;
2022-11-01 19:35:38 +00:00
auto returned_pending_response = PendingResponse : : create ( vm , request ) ;
2022-10-23 22:16:14 +01:00
// 2. Let corsWithPreflightResponse be the result of running HTTP fetch given fetchParams and true.
auto cors_with_preflight_response = TRY ( http_fetch ( realm , fetch_params , MakeCORSPreflight : : Yes ) ) ;
2024-11-15 04:01:23 +13:00
cors_with_preflight_response - > when_loaded ( [ returned_pending_response ] ( GC : : Ref < Infrastructure : : Response > cors_with_preflight_response ) {
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'main fetch' cors_with_preflight_response load callback " ) ;
// 3. If corsWithPreflightResponse is a network error, then clear cache entries using request.
if ( cors_with_preflight_response - > is_network_error ( ) ) {
// FIXME: Clear cache entries
}
// 4. Return corsWithPreflightResponse.
returned_pending_response - > resolve ( cors_with_preflight_response ) ;
} ) ;
return returned_pending_response ;
}
2025-04-23 11:07:04 -04:00
2022-10-23 22:16:14 +01:00
// -> Otherwise
2025-04-23 11:07:04 -04:00
// 1. Set request’ s response tainting to "cors".
request - > set_response_tainting ( Infrastructure : : Request : : ResponseTainting : : CORS ) ;
2022-10-23 22:16:14 +01:00
2025-04-23 11:07:04 -04:00
// 2. Return the result of running HTTP fetch given fetchParams.
return http_fetch ( realm , fetch_params ) ;
2024-10-31 05:23:43 +13:00
} ) ;
2022-10-23 22:16:14 +01:00
if ( recursive = = Recursive : : Yes ) {
2023-04-19 15:49:15 +01:00
// 12. If response is null, then set response to the result of running the steps corresponding to the first
2022-10-23 22:16:14 +01:00
// matching statement:
2023-03-07 18:32:07 +00:00
auto pending_response = ! response
2024-10-31 05:23:43 +13:00
? TRY ( get_response - > function ( ) ( ) )
2022-11-01 19:35:38 +00:00
: PendingResponse : : create ( vm , request , * response ) ;
2022-10-23 22:16:14 +01:00
2023-04-19 15:49:15 +01:00
// 13. If recursive is true, then return response.
2022-10-23 22:16:14 +01:00
return pending_response ;
}
2023-04-19 15:49:15 +01:00
// 11. If recursive is false, then run the remaining steps in parallel.
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & realm , & vm , & fetch_params , request , response , get_response ] {
2023-04-19 15:49:15 +01:00
// 12. If response is null, then set response to the result of running the steps corresponding to the first
2022-10-23 22:16:14 +01:00
// matching statement:
2022-11-01 19:35:38 +00:00
auto pending_response = PendingResponse : : create ( vm , request , Infrastructure : : Response : : create ( vm ) ) ;
2022-10-23 22:16:14 +01:00
if ( ! response ) {
2024-10-31 05:23:43 +13:00
auto pending_response_or_error = get_response - > function ( ) ( ) ;
2022-10-23 22:16:14 +01:00
if ( pending_response_or_error . is_error ( ) )
return ;
pending_response = pending_response_or_error . release_value ( ) ;
}
2024-11-15 04:01:23 +13:00
pending_response - > when_loaded ( [ & realm , & vm , & fetch_params , request , response , response_was_null = ! response ] ( GC : : Ref < Infrastructure : : Response > resolved_response ) mutable {
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'main fetch' pending_response load callback " ) ;
if ( response_was_null )
response = resolved_response ;
2023-04-19 15:49:15 +01:00
// 14. If response is not a network error and response is not a filtered response, then:
2022-10-23 22:16:14 +01:00
if ( ! response - > is_network_error ( ) & & ! is < Infrastructure : : FilteredResponse > ( * response ) ) {
// 1. If request’ s response tainting is "cors", then:
if ( request - > response_tainting ( ) = = Infrastructure : : Request : : ResponseTainting : : CORS ) {
2023-08-01 21:40:30 +12:00
// 1. Let headerNames be the result of extracting header list values given
// `Access-Control-Expose-Headers` and response’ s header list.
2024-04-26 13:24:20 -04:00
auto header_names_or_failure = Infrastructure : : extract_header_list_values ( " Access-Control-Expose-Headers " sv . bytes ( ) , response - > header_list ( ) ) ;
2023-08-01 21:40:30 +12:00
auto header_names = header_names_or_failure . has < Vector < ByteBuffer > > ( ) ? header_names_or_failure . get < Vector < ByteBuffer > > ( ) : Vector < ByteBuffer > { } ;
// 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 ( ) ) ) {
2024-04-26 13:24:20 -04:00
auto unique_header_names = response - > header_list ( ) - > unique_names ( ) ;
2023-08-01 21:40:30 +12:00
response - > set_cors_exposed_header_name_list ( move ( unique_header_names ) ) ;
}
// 3. Otherwise, if headerNames is not null or failure, then set response’ s CORS-exposed
// header-name list to headerNames.
else if ( ! header_names . is_empty ( ) ) {
response - > set_cors_exposed_header_name_list ( move ( header_names ) ) ;
}
2022-10-23 22:16:14 +01:00
}
// 2. Set response to the following filtered response with response as its internal response, depending
// on request’ s response tainting:
2024-11-15 04:01:23 +13:00
response = [ & ] ( ) - > GC : : Ref < Infrastructure : : Response > {
2022-10-23 22:16:14 +01:00
switch ( request - > response_tainting ( ) ) {
// -> "basic"
case Infrastructure : : Request : : ResponseTainting : : Basic :
// basic filtered response
2024-04-26 13:42:39 -04:00
return Infrastructure : : BasicFilteredResponse : : create ( vm , * response ) ;
2022-10-23 22:16:14 +01:00
// -> "cors"
case Infrastructure : : Request : : ResponseTainting : : CORS :
// CORS filtered response
2024-04-26 13:42:39 -04:00
return Infrastructure : : CORSFilteredResponse : : create ( vm , * response ) ;
2022-10-23 22:16:14 +01:00
// -> "opaque"
case Infrastructure : : Request : : ResponseTainting : : Opaque :
// opaque filtered response
return Infrastructure : : OpaqueFilteredResponse : : create ( vm , * response ) ;
default :
VERIFY_NOT_REACHED ( ) ;
}
2024-04-26 13:42:39 -04:00
} ( ) ;
2022-10-23 22:16:14 +01:00
}
2023-04-19 15:49:15 +01:00
// 15. Let internalResponse be response, if response is a network error, and response’ s internal response
2022-10-23 22:16:14 +01:00
// otherwise.
auto internal_response = response - > is_network_error ( )
2024-11-15 04:01:23 +13:00
? GC : : Ref { * response }
2022-10-23 22:16:14 +01:00
: static_cast < Infrastructure : : FilteredResponse & > ( * response ) . internal_response ( ) ;
2023-04-19 15:49:15 +01:00
// 16. If internalResponse’ s URL list is empty, then set it to a clone of request’ s URL list.
2022-10-23 22:16:14 +01:00
// NOTE: A response’ s URL list can be empty (for example, when the response represents an about URL).
if ( internal_response - > url_list ( ) . is_empty ( ) )
internal_response - > set_url_list ( request - > url_list ( ) ) ;
2025-08-10 19:41:30 +02:00
// 17. Set internalResponse’ s redirect taint to request’ s redirect-taint.
internal_response - > set_redirect_taint ( request - > redirect_taint ( ) ) ;
2022-10-23 22:16:14 +01:00
2023-04-19 15:49:15 +01:00
// 18. If request’ s timing allow failed flag is unset, then set internalResponse’ s timing allow passed flag.
2022-10-23 22:16:14 +01:00
if ( ! request - > timing_allow_failed ( ) )
internal_response - > set_timing_allow_passed ( true ) ;
2023-04-19 15:49:15 +01:00
// 19. If response is not a network error and any of the following returns blocked
2022-10-23 22:16:14 +01:00
if ( ! response - > is_network_error ( ) & & (
2024-05-29 19:40:01 +01:00
// - should internalResponse to request be blocked as mixed content
MixedContent : : should_response_to_request_be_blocked_as_mixed_content ( request , internal_response ) = = Infrastructure : : RequestOrResponseBlocking : : Blocked
2024-11-28 12:30:36 +00:00
// - should internalResponse to request be blocked by Content Security Policy
| | ContentSecurityPolicy : : should_response_to_request_be_blocked_by_content_security_policy ( realm , internal_response , request ) = = ContentSecurityPolicy : : Directives : : Directive : : Result : : Blocked
2022-10-23 22:16:14 +01:00
// - should internalResponse to request be blocked due to its MIME type
2024-04-26 13:24:20 -04:00
| | Infrastructure : : should_response_to_request_be_blocked_due_to_its_mime_type ( internal_response , request ) = = Infrastructure : : RequestOrResponseBlocking : : Blocked
2022-10-23 22:16:14 +01:00
// - should internalResponse to request be blocked due to nosniff
2024-04-26 13:24:20 -04:00
| | Infrastructure : : should_response_to_request_be_blocked_due_to_nosniff ( internal_response , request ) = = Infrastructure : : RequestOrResponseBlocking : : Blocked ) ) {
2022-10-23 22:16:14 +01:00
// then set response and internalResponse to a network error.
2023-08-07 11:12:38 +02:00
response = internal_response = Infrastructure : : Response : : network_error ( vm , " Response was blocked " _string ) ;
2022-10-23 22:16:14 +01:00
}
2023-04-19 15:49:15 +01:00
// 20. If response’ s type is "opaque", internalResponse’ s status is 206, internalResponse’ s range-requested
2022-10-23 22:16:14 +01:00
// flag is set, and request’ s header list does not contain `Range`, then set response and
// internalResponse to a network error.
// NOTE: Traditionally, APIs accept a ranged response even if a range was not requested. This prevents a
// partial response from an earlier ranged request being provided to an API that did not make a range
// request.
if ( response - > type ( ) = = Infrastructure : : Response : : Type : : Opaque
& & internal_response - > status ( ) = = 206
& & internal_response - > range_requested ( )
& & ! request - > header_list ( ) - > contains ( " Range " sv . bytes ( ) ) ) {
2023-08-07 11:12:38 +02:00
response = internal_response = Infrastructure : : Response : : network_error ( vm , " Response has status 206 and 'range-requested' flag set, but request has no 'Range' header " _string ) ;
2022-10-23 22:16:14 +01:00
}
2023-04-19 15:49:15 +01:00
// 21. If response is not a network error and either request’ s method is `HEAD` or `CONNECT`, or
2022-10-23 22:16:14 +01:00
// 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 ( ) ) ) )
internal_response - > set_body ( { } ) ;
2023-04-19 15:49:15 +01:00
// 22. If request’ s integrity metadata is not the empty string, then:
2022-10-23 22:16:14 +01:00
if ( ! request - > integrity_metadata ( ) . is_empty ( ) ) {
// 1. Let processBodyError be this step: run fetch response handover given fetchParams and a network
// error.
2024-11-15 04:01:23 +13:00
auto process_body_error = GC : : create_function ( vm . heap ( ) , [ & realm , & vm , & fetch_params ] ( JS : : Value ) {
2025-04-02 20:51:45 +13:00
fetch_response_handover ( realm , fetch_params , Infrastructure : : Response : : network_error ( vm , " Response body could not be processed " _string ) ) ;
2024-04-23 10:25:20 +02:00
} ) ;
2022-10-23 22:16:14 +01:00
// 2. If response’ s body is null, then run processBodyError and abort these steps.
2023-08-18 19:38:13 +02:00
if ( ! response - > body ( ) ) {
2024-04-23 10:25:20 +02:00
process_body_error - > function ( ) ( { } ) ;
2022-10-23 22:16:14 +01:00
return ;
}
2023-04-20 16:52:01 +01:00
// 3. Let processBody given bytes be these steps:
2024-04-30 07:24:37 -04:00
auto process_body = GC : : create_function ( vm . heap ( ) , [ & realm , request , response , & fetch_params , process_body_error ] ( ByteBuffer bytes ) {
2023-04-20 16:52:01 +01:00
// 1. If bytes do not match request’ s integrity metadata, then run processBodyError and abort these steps.
if ( ! TRY_OR_IGNORE ( SRI : : do_bytes_match_metadata_list ( bytes , request - > integrity_metadata ( ) ) ) ) {
2024-04-23 10:25:20 +02:00
process_body_error - > function ( ) ( { } ) ;
2023-04-20 16:52:01 +01:00
return ;
}
// 2. Set response’ s body to bytes as a body.
2024-11-04 16:06:01 +01:00
response - > set_body ( Infrastructure : : byte_sequence_as_body ( realm , bytes ) ) ;
2023-04-20 16:52:01 +01:00
// 3. Run fetch response handover given fetchParams and response.
2024-04-26 14:57:40 -04:00
fetch_response_handover ( realm , fetch_params , * response ) ;
2024-04-23 10:25:20 +02:00
} ) ;
2022-10-23 22:16:14 +01:00
2023-04-20 16:52:01 +01:00
// 4. Fully read response’ s body given processBody and processBodyError.
2024-04-26 14:57:40 -04:00
response - > body ( ) - > fully_read ( realm , process_body , process_body_error , fetch_params . task_destination ( ) ) ;
2022-10-23 22:16:14 +01:00
}
2023-04-19 15:49:15 +01:00
// 23. Otherwise, run fetch response handover given fetchParams and response.
2022-10-23 22:16:14 +01:00
else {
2024-04-26 14:57:40 -04:00
fetch_response_handover ( realm , fetch_params , * response ) ;
2022-10-23 22:16:14 +01:00
}
} ) ;
2024-10-31 02:39:29 +13:00
} ) ) ;
2022-10-23 22:16:14 +01:00
2024-11-15 04:01:23 +13:00
return GC : : Ptr < PendingResponse > { } ;
2022-10-23 22:16:14 +01:00
}
2025-01-19 19:02:18 +13:00
// https://fetch.spec.whatwg.org/#request-determine-the-environment
static GC : : Ptr < HTML : : Environment > determine_the_environment ( GC : : Ref < Infrastructure : : Request > request )
{
// 1. If request’ s reserved client is non-null, then return request’ s reserved client.
if ( request - > reserved_client ( ) )
return request - > reserved_client ( ) ;
// 2. If request’ s client is non-null, then return request’ s client.
if ( request - > client ( ) )
return request - > client ( ) ;
// 3. Return null.
return { } ;
}
2022-10-23 22:16:14 +01:00
// https://fetch.spec.whatwg.org/#fetch-finale
2024-04-26 14:57:40 -04:00
void fetch_response_handover ( JS : : Realm & realm , Infrastructure : : FetchParams const & fetch_params , Infrastructure : : Response & response )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'fetch response handover' with: fetch_params @ {}, response @ {} " , & fetch_params , & response ) ;
auto & vm = realm . vm ( ) ;
// 1. Let timingInfo be fetchParams’ s timing info.
auto timing_info = fetch_params . timing_info ( ) ;
// 2. If response is not a network error and fetchParams’ s request’ s client is a secure context, then set
// timingInfo’ s server-timing headers to the result of getting, decoding, and splitting `Server-Timing` from
// response’ s header list.
// The user agent may decide to expose `Server-Timing` headers to non-secure contexts requests as well.
2023-02-26 16:09:02 -07:00
auto client = fetch_params . request ( ) - > client ( ) ;
2022-10-23 22:16:14 +01:00
if ( ! response . is_network_error ( ) & & client ! = nullptr & & HTML : : is_secure_context ( * client ) ) {
2024-04-26 13:24:20 -04:00
auto server_timing_headers = response . header_list ( ) - > get_decode_and_split ( " Server-Timing " sv . bytes ( ) ) ;
2022-10-23 22:16:14 +01:00
if ( server_timing_headers . has_value ( ) )
timing_info - > set_server_timing_headers ( server_timing_headers . release_value ( ) ) ;
}
// 3. Let processResponseEndOfBody be the following steps:
auto process_response_end_of_body = [ & vm , & response , & fetch_params , timing_info ] {
// 1. Let unsafeEndTime be the unsafe shared current time.
auto unsafe_end_time = HighResolutionTime : : unsafe_shared_current_time ( ) ;
// 2. If fetchParams’ s request’ s destination is "document", then set fetchParams’ s controller’ s full timing
// info to fetchParams’ s timing info.
if ( fetch_params . request ( ) - > destination ( ) = = Infrastructure : : Request : : Destination : : Document )
fetch_params . controller ( ) - > set_full_timing_info ( fetch_params . timing_info ( ) ) ;
// 3. Set fetchParams’ s controller’ s report timing steps to the following steps given a global object global:
2025-02-26 15:02:49 +00:00
fetch_params . controller ( ) - > set_report_timing_steps ( [ & vm , & response , & fetch_params , timing_info , unsafe_end_time ] ( JS : : Object & global ) mutable {
2022-10-23 22:16:14 +01:00
// 1. If fetchParams’ s request’ s URL’ s scheme is not an HTTP(S) scheme, then return.
if ( ! Infrastructure : : is_http_or_https_scheme ( fetch_params . request ( ) - > url ( ) . scheme ( ) ) )
return ;
// 2. Set timingInfo’ s end time to the relative high resolution time given unsafeEndTime and global.
2025-02-26 15:46:41 +00:00
// Spec Issue: Using relative time here is incorrect, as end time is converted to relative time by Resource Timing,
// causing it to take a relative time of an already relative time, effectively make it always a negative
// value approximately the value of the time origin.
timing_info - > set_end_time ( unsafe_end_time ) ;
2022-10-23 22:16:14 +01:00
// 3. Let cacheState be response’ s cache state.
auto cache_state = response . cache_state ( ) ;
// 4. Let bodyInfo be response’ s body info.
auto body_info = response . body_info ( ) ;
// 5. If response’ s timing allow passed flag is not set, then set timingInfo to the result of creating an
// opaque timing info for timingInfo, set bodyInfo to a new response body info, and set cacheState to
// the empty string.
// NOTE: This covers the case of response being a network error.
if ( ! response . timing_allow_passed ( ) ) {
timing_info = Infrastructure : : create_opaque_timing_info ( vm , timing_info ) ;
body_info = Infrastructure : : Response : : BodyInfo { } ;
cache_state = { } ;
}
2024-05-19 13:34:15 +01:00
// 6. Let responseStatus be 0.
auto response_status = 0 ;
2022-10-23 22:16:14 +01:00
2025-08-10 19:41:30 +02:00
// 7. If fetchParams’ s request’ s mode is not "navigate" or response’ s redirect taint is "same-origin":
if ( fetch_params . request ( ) - > mode ( ) ! = Infrastructure : : Request : : Mode : : Navigate | | response . redirect_taint ( ) = = Infrastructure : : RedirectTaint : : SameOrigin ) {
2024-05-19 13:34:15 +01:00
// 1. Set responseStatus to response’ s status.
response_status = response . status ( ) ;
// 2. Let mimeType be the result of extracting a MIME type from response’ s header list.
2024-05-19 13:23:38 +01:00
auto mime_type = response . header_list ( ) - > extract_mime_type ( ) ;
2024-05-19 13:34:15 +01:00
// 3. If mimeType is non-null, then set bodyInfo’ s content type to the result of minimizing a supported MIME type given mimeType.
2024-05-19 13:23:38 +01:00
if ( mime_type . has_value ( ) )
body_info . content_type = MimeSniff : : minimise_a_supported_mime_type ( mime_type . value ( ) ) ;
}
2025-02-26 15:51:05 +00:00
// 8. If fetchParams’ s request’ s initiator type is not null, then mark resource timing given timingInfo,
// request’ s URL, request’ s initiator type, global, cacheState, bodyInfo, and responseStatus.
if ( fetch_params . request ( ) - > initiator_type ( ) . has_value ( ) ) {
ResourceTiming : : PerformanceResourceTiming : : mark_resource_timing ( timing_info , fetch_params . request ( ) - > url ( ) . to_string ( ) , Infrastructure : : initiator_type_to_string ( fetch_params . request ( ) - > initiator_type ( ) . value ( ) ) , global , cache_state , body_info , response_status ) ;
}
2022-10-23 22:16:14 +01:00
} ) ;
// 4. Let processResponseEndOfBodyTask be the following steps:
2024-11-15 04:01:23 +13:00
auto process_response_end_of_body_task = GC : : create_function ( vm . heap ( ) , [ & fetch_params , & response ] {
2022-10-23 22:16:14 +01:00
// 1. Set fetchParams’ s request’ s done flag.
fetch_params . request ( ) - > set_done ( true ) ;
// 2. If fetchParams’ s process response end-of-body is non-null, then run fetchParams’ s process response
// end-of-body given response.
2023-08-18 18:26:08 +02:00
if ( fetch_params . algorithms ( ) - > process_response_end_of_body ( ) )
( fetch_params . algorithms ( ) - > process_response_end_of_body ( ) ) ( response ) ;
2022-10-23 22:16:14 +01:00
// 3. If fetchParams’ s request’ s initiator type is non-null and fetchParams’ s request’ s client’ s global
// object is fetchParams’ s task destination, then run fetchParams’ s controller’ s report timing steps
// given fetchParams’ s request’ s client’ s global object.
2023-02-26 16:09:02 -07:00
auto client = fetch_params . request ( ) - > client ( ) ;
2024-11-15 04:01:23 +13:00
auto const * task_destination_global_object = fetch_params . task_destination ( ) . get_pointer < GC : : Ref < JS : : Object > > ( ) ;
2022-10-23 22:16:14 +01:00
if ( client ! = nullptr & & task_destination_global_object ! = nullptr ) {
if ( fetch_params . request ( ) - > initiator_type ( ) . has_value ( ) & & & client - > global_object ( ) = = task_destination_global_object - > ptr ( ) )
fetch_params . controller ( ) - > report_timing ( client - > global_object ( ) ) ;
}
2024-04-19 10:23:40 +02:00
} ) ;
2022-10-23 22:16:14 +01:00
// 5. Queue a fetch task to run processResponseEndOfBodyTask with fetchParams’ s task destination.
2025-07-16 12:29:26 +02:00
Infrastructure : : queue_fetch_task ( fetch_params . controller ( ) , fetch_params . task_destination ( ) , move ( process_response_end_of_body_task ) ) ;
2022-10-23 22:16:14 +01:00
} ;
// 4. If fetchParams’ s process response is non-null, then queue a fetch task to run fetchParams’ s process response
// given response, with fetchParams’ s task destination.
2023-08-18 18:26:08 +02:00
if ( fetch_params . algorithms ( ) - > process_response ( ) ) {
2025-07-16 12:29:26 +02:00
Infrastructure : : queue_fetch_task ( fetch_params . controller ( ) , fetch_params . task_destination ( ) , GC : : create_function ( vm . heap ( ) , [ & fetch_params , & response ] ( ) {
2023-08-18 18:26:08 +02:00
fetch_params . algorithms ( ) - > process_response ( ) ( response ) ;
2024-04-19 10:23:40 +02:00
} ) ) ;
2022-10-23 22:16:14 +01:00
}
2023-05-26 09:42:26 -04:00
// 5. Let internalResponse be response, if response is a network error; otherwise response’ s internal response.
2024-11-15 04:01:23 +13:00
auto internal_response = response . is_network_error ( ) ? GC : : Ref { response } : response . unsafe_response ( ) ;
2023-05-26 09:42:26 -04:00
// 6. If internalResponse’ s body is null, then run processResponseEndOfBody.
2023-08-18 19:38:13 +02:00
if ( ! internal_response - > body ( ) ) {
2022-10-23 22:16:14 +01:00
process_response_end_of_body ( ) ;
}
2023-05-26 09:42:26 -04:00
// 7. Otherwise:
2022-10-23 22:16:14 +01:00
else {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext const execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-04-27 10:00:47 +02:00
// 1. Let transformStream be a new TransformStream.
2024-11-14 05:50:17 +13:00
auto transform_stream = realm . create < Streams : : TransformStream > ( realm ) ;
2024-04-27 10:00:47 +02:00
// 2. Let identityTransformAlgorithm be an algorithm which, given chunk, enqueues chunk in transformStream.
2024-11-15 04:01:23 +13:00
auto identity_transform_algorithm = GC : : create_function ( realm . heap ( ) , [ & realm , transform_stream ] ( JS : : Value chunk ) - > GC : : Ref < WebIDL : : Promise > {
2024-04-27 10:00:47 +02:00
MUST ( Streams : : transform_stream_default_controller_enqueue ( * transform_stream - > controller ( ) , chunk ) ) ;
return WebIDL : : create_resolved_promise ( realm , JS : : js_undefined ( ) ) ;
} ) ;
// 3. Set up transformStream with transformAlgorithm set to identityTransformAlgorithm and flushAlgorithm set
// to processResponseEndOfBody.
2024-11-15 04:01:23 +13:00
auto flush_algorithm = GC : : create_function ( realm . heap ( ) , [ & realm , process_response_end_of_body ] ( ) - > GC : : Ref < WebIDL : : Promise > {
2024-04-27 10:00:47 +02:00
process_response_end_of_body ( ) ;
return WebIDL : : create_resolved_promise ( realm , JS : : js_undefined ( ) ) ;
} ) ;
2024-12-08 17:35:07 +13:00
transform_stream - > set_up ( identity_transform_algorithm , flush_algorithm ) ;
2024-04-27 10:00:47 +02:00
// 4. Set internalResponse’ s body’ s stream to the result of internalResponse’ s body’ s stream piped through transformStream.
2024-12-26 12:02:07 +13:00
internal_response - > body ( ) - > set_stream ( internal_response - > body ( ) - > stream ( ) - > piped_through ( transform_stream ) ) ;
2022-10-23 22:16:14 +01:00
}
2023-05-26 09:42:26 -04:00
// 8. If fetchParams’ s process response consume body is non-null, then:
2023-08-18 18:26:08 +02:00
if ( fetch_params . algorithms ( ) - > process_response_consume_body ( ) ) {
2022-10-23 22:16:14 +01:00
// 1. Let processBody given nullOrBytes be this step: run fetchParams’ s process response consume body given
// response and nullOrBytes.
2024-11-15 04:01:23 +13:00
auto process_body = GC : : create_function ( vm . heap ( ) , [ & fetch_params , & response ] ( ByteBuffer null_or_bytes ) {
2023-08-18 18:26:08 +02:00
( fetch_params . algorithms ( ) - > process_response_consume_body ( ) ) ( response , null_or_bytes ) ;
2024-04-23 10:25:20 +02:00
} ) ;
2022-10-23 22:16:14 +01:00
// 2. Let processBodyError be this step: run fetchParams’ s process response consume body given response and
// failure.
2024-11-15 04:01:23 +13:00
auto process_body_error = GC : : create_function ( vm . heap ( ) , [ & fetch_params , & response ] ( JS : : Value ) {
2023-08-18 18:26:08 +02:00
( fetch_params . algorithms ( ) - > process_response_consume_body ( ) ) ( response , Infrastructure : : FetchAlgorithms : : ConsumeBodyFailureTag { } ) ;
2024-04-23 10:25:20 +02:00
} ) ;
2022-10-23 22:16:14 +01:00
2023-05-26 09:42:26 -04:00
// 3. If internalResponse's body is null, then queue a fetch task to run processBody given null, with
// fetchParams’ s task destination.
2023-08-18 19:38:13 +02:00
if ( ! internal_response - > body ( ) ) {
2025-07-16 12:29:26 +02:00
Infrastructure : : queue_fetch_task ( fetch_params . controller ( ) , fetch_params . task_destination ( ) , GC : : create_function ( vm . heap ( ) , [ process_body ] ( ) {
2024-04-23 10:25:20 +02:00
process_body - > function ( ) ( { } ) ;
2024-04-19 10:23:40 +02:00
} ) ) ;
2022-10-23 22:16:14 +01:00
}
2023-05-26 09:42:26 -04:00
// 4. Otherwise, fully read internalResponse body given processBody, processBodyError, and fetchParams’ s task
2023-02-28 18:48:59 +00:00
// destination.
2022-10-23 22:16:14 +01:00
else {
2024-04-26 14:57:40 -04:00
internal_response - > body ( ) - > fully_read ( realm , process_body , process_body_error , fetch_params . task_destination ( ) ) ;
2022-10-23 22:16:14 +01:00
}
}
}
// https://fetch.spec.whatwg.org/#concept-scheme-fetch
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < PendingResponse > > scheme_fetch ( JS : : Realm & realm , Infrastructure : : FetchParams const & fetch_params )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'scheme fetch' with: fetch_params @ {} " , & fetch_params ) ;
auto & vm = realm . vm ( ) ;
// 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
if ( fetch_params . is_canceled ( ) )
2023-03-03 18:02:43 +00:00
return PendingResponse : : create ( vm , fetch_params . request ( ) , Infrastructure : : Response : : appropriate_network_error ( vm , fetch_params ) ) ;
2022-10-23 22:16:14 +01:00
// 2. Let request be fetchParams’ s request.
auto request = fetch_params . request ( ) ;
// 3. Switch on request’ s current URL’ s scheme and run the associated steps:
// -> "about"
if ( request - > current_url ( ) . scheme ( ) = = " about " sv ) {
// If request’ s current URL’ s path is the string "blank", then return a new response whose status message is
// `OK`, header list is « (`Content-Type`, `text/html;charset=utf-8`) », and body is the empty byte sequence as
// a body.
// NOTE: URLs such as "about:config" are handled during navigation and result in a network error in the context
// of fetching.
2024-08-05 15:14:00 +12:00
if ( request - > current_url ( ) . paths ( ) . size ( ) = = 1 & & request - > current_url ( ) . paths ( ) [ 0 ] = = " blank " sv ) {
2022-10-23 22:16:14 +01:00
auto response = Infrastructure : : Response : : create ( vm ) ;
response - > set_status_message ( MUST ( ByteBuffer : : copy ( " OK " sv . bytes ( ) ) ) ) ;
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Content-Type " sv , " text/html;charset=utf-8 " sv ) ;
response - > header_list ( ) - > append ( move ( header ) ) ;
2024-11-04 16:06:01 +01:00
response - > set_body ( Infrastructure : : byte_sequence_as_body ( realm , " " sv . bytes ( ) ) ) ;
2022-11-01 19:35:38 +00:00
return PendingResponse : : create ( vm , request , response ) ;
2022-10-23 22:16:14 +01:00
}
2024-01-12 19:40:41 +01:00
// FIXME: This is actually wrong, see note above.
return TRY ( nonstandard_resource_loader_file_or_http_network_fetch ( realm , fetch_params ) ) ;
2022-10-23 22:16:14 +01:00
}
// -> "blob"
else if ( request - > current_url ( ) . scheme ( ) = = " blob " sv ) {
2023-08-01 18:53:39 -04:00
// 1. Let blobURLEntry be request’ s current URL’ s blob URL entry.
2024-05-05 20:39:13 +12:00
auto const & blob_url_entry = request - > current_url ( ) . blob_url_entry ( ) ;
2023-08-01 18:53:39 -04:00
2025-01-19 19:02:18 +13:00
// 2. If request’ s method is not `GET` or blobURLEntry is null, then return a network error. [FILEAPI]
2025-08-18 00:37:34 +02:00
if ( request - > method ( ) ! = " GET " sv . bytes ( ) | | ! blob_url_entry . has_value ( ) )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request has an invalid 'blob:' URL " _string ) ) ;
2023-08-01 18:53:39 -04:00
2025-01-19 19:02:18 +13:00
// 3. Let requestEnvironment be the result of determining the environment given request.
auto request_environment = determine_the_environment ( request ) ;
// 4. Let isTopLevelNavigation be true if request’ s destination is "document"; otherwise, false.
bool is_top_level_navigation = request - > destination ( ) = = Infrastructure : : Request : : Destination : : Document ;
// 5. If isTopLevelNavigation is false and requestEnvironment is null, then return a network error.
if ( ! is_top_level_navigation & & ! request_environment )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request is missing fetch client " _string ) ) ;
2025-01-19 19:02:18 +13:00
// 6. Let navigationOrEnvironment be the string "navigation" if isTopLevelNavigation is true; otherwise, requestEnvironment.
auto navigation_or_environment = [ & ] ( ) - > Variant < FileAPI : : NavigationEnvironment , GC : : Ref < HTML : : Environment > > {
if ( is_top_level_navigation )
return FileAPI : : NavigationEnvironment { } ;
return GC : : Ref { * request_environment } ;
} ( ) ;
// 7. Let blob be the result of obtaining a blob object given blobURLEntry and navigationOrEnvironment.
2025-08-18 00:37:34 +02:00
auto maybe_blob_object = FileAPI : : obtain_a_blob_object ( blob_url_entry . value ( ) , navigation_or_environment ) ;
2025-01-19 19:02:18 +13:00
// 8. If blob is not a Blob object, then return a network error.
2025-08-18 00:37:34 +02:00
if ( ! maybe_blob_object . has_value ( ) )
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Failed to obtain a Blob object from 'blob:' URL " _string ) ) ;
URL : : BlobURLEntry : : Blob * blob_object ;
if ( blob_object = maybe_blob_object . value ( ) . get_pointer < URL : : BlobURLEntry : : Blob > ( ) ; ! blob_object )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Failed to obtain a Blob object from 'blob:' URL " _string ) ) ;
2025-01-19 19:02:18 +13:00
auto const blob = FileAPI : : Blob : : create ( realm , blob_object - > data , blob_object - > type ) ;
2023-08-01 18:53:39 -04:00
2025-01-19 19:02:18 +13:00
// 9. Let response be a new response.
2023-08-01 18:53:39 -04:00
auto response = Infrastructure : : Response : : create ( vm ) ;
2025-01-19 19:02:18 +13:00
// 10. Let fullLength be blob’ s size.
2023-08-01 18:53:39 -04:00
auto full_length = blob - > size ( ) ;
2025-01-19 19:02:18 +13:00
// 11. Let serializedFullLength be fullLength, serialized and isomorphic encoded.
2024-10-14 10:05:01 +02:00
auto serialized_full_length = String : : number ( full_length ) ;
2023-08-01 18:53:39 -04:00
2025-01-19 19:02:18 +13:00
// 12. Let type be blob’ s type.
2023-08-01 18:53:39 -04:00
auto const & type = blob - > type ( ) ;
2025-01-19 19:02:18 +13:00
// 13. If request’ s header list does not contain `Range`:
2023-08-01 18:53:39 -04:00
if ( ! request - > header_list ( ) - > contains ( " Range " sv . bytes ( ) ) ) {
// 1. Let bodyWithType be the result of safely extracting blob.
2024-11-04 16:06:01 +01:00
auto body_with_type = safely_extract_body ( realm , blob - > raw_bytes ( ) ) ;
2023-08-01 18:53:39 -04:00
// 2. Set response’ s status message to `OK`.
response - > set_status_message ( MUST ( ByteBuffer : : copy ( " OK " sv . bytes ( ) ) ) ) ;
// 3. Set response’ s body to bodyWithType’ s body.
2024-11-04 16:06:01 +01:00
response - > set_body ( body_with_type . body ) ;
2023-08-01 18:53:39 -04:00
// 4. Set response’ s header list to « (`Content-Length`, serializedFullLength), (`Content-Type`, type) ».
2024-04-26 13:24:20 -04:00
auto content_length_header = Infrastructure : : Header : : from_string_pair ( " Content-Length " sv , serialized_full_length ) ;
response - > header_list ( ) - > append ( move ( content_length_header ) ) ;
2023-08-01 18:53:39 -04:00
2024-04-26 13:24:20 -04:00
auto content_type_header = Infrastructure : : Header : : from_string_pair ( " Content-Type " sv , type ) ;
response - > header_list ( ) - > append ( move ( content_type_header ) ) ;
2023-08-01 18:53:39 -04:00
}
2025-01-19 19:02:18 +13:00
// 14. Otherwise:
2023-08-01 18:53:39 -04:00
else {
// 1. Set response’ s range-requested flag.
2024-11-18 17:34:30 -06:00
response - > set_range_requested ( true ) ;
2023-08-01 18:53:39 -04:00
// 2. Let rangeHeader be the result of getting `Range` from request’ s header list.
2024-11-18 17:34:30 -06:00
auto const range_header = request - > header_list ( ) - > get ( " Range " sv . bytes ( ) ) . value_or ( ByteBuffer { } ) ;
2023-08-01 18:53:39 -04:00
// 3. Let rangeValue be the result of parsing a single range header value given rangeHeader and true.
2024-11-18 17:34:30 -06:00
auto maybe_range_value = Infrastructure : : parse_single_range_header_value ( range_header , true ) ;
2023-08-01 18:53:39 -04:00
// 4. If rangeValue is failure, then return a network error.
2024-11-18 17:34:30 -06:00
if ( ! maybe_range_value . has_value ( ) )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Failed to parse single range header value " _string ) ) ;
2024-11-18 17:34:30 -06:00
2023-08-01 18:53:39 -04:00
// 5. Let (rangeStart, rangeEnd) be rangeValue.
2024-11-18 17:34:30 -06:00
auto & [ range_start , range_end ] = maybe_range_value . value ( ) ;
2023-08-01 18:53:39 -04:00
// 6. If rangeStart is null:
2024-11-18 17:34:30 -06:00
if ( ! range_start . has_value ( ) ) {
VERIFY ( range_end . has_value ( ) ) ;
// 1. Set rangeStart to fullLength − rangeEnd.
range_start = full_length - * range_end ;
// 2. Set rangeEnd to rangeStart + rangeEnd − 1.
range_end = * range_start + * range_end - 1 ;
}
2023-08-01 18:53:39 -04:00
// 7. Otherwise:
2024-11-18 17:34:30 -06:00
else {
// 1. If rangeStart is greater than or equal to fullLength, then return a network error.
if ( * range_start > = full_length )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " rangeStart is greater than or equal to fullLength " _string ) ) ;
2024-11-18 17:34:30 -06:00
// 2. If rangeEnd is null or rangeEnd is greater than or equal to fullLength, then set rangeEnd to fullLength − 1.
if ( ! range_end . has_value ( ) | | * range_end > = full_length )
range_end = full_length - 1 ;
}
2023-08-01 18:53:39 -04:00
// 8. Let slicedBlob be the result of invoking slice blob given blob, rangeStart, rangeEnd + 1, and type.
2024-11-18 17:34:30 -06:00
auto sliced_blob = TRY ( blob - > slice ( * range_start , * range_end + 1 , type ) ) ;
2023-08-01 18:53:39 -04:00
// 9. Let slicedBodyWithType be the result of safely extracting slicedBlob.
2024-11-04 16:06:01 +01:00
auto sliced_body_with_type = safely_extract_body ( realm , sliced_blob - > raw_bytes ( ) ) ;
2024-11-18 17:34:30 -06:00
2023-08-01 18:53:39 -04:00
// 10. Set response’ s body to slicedBodyWithType’ s body.
2024-11-18 17:34:30 -06:00
response - > set_body ( sliced_body_with_type . body ) ;
2023-08-01 18:53:39 -04:00
// 11. Let serializedSlicedLength be slicedBlob’ s size, serialized and isomorphic encoded.
2024-11-18 17:34:30 -06:00
auto serialized_sliced_length = String : : number ( sliced_blob - > size ( ) ) ;
// 12. Let contentRange be the result of invoking build a content range given rangeStart, rangeEnd, and fullLength.
auto content_range = Infrastructure : : build_content_range ( * range_start , * range_end , full_length ) ;
// 13. Set response’ s status to 206.
response - > set_status ( 206 ) ;
// 14. Set response’ s status message to `Partial Content`.
response - > set_status_message ( MUST ( ByteBuffer : : copy ( " Partial Content " sv . bytes ( ) ) ) ) ;
// 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 ) ;
response - > header_list ( ) - > append ( move ( content_length_header ) ) ;
// (`Content-Type`, type),
auto content_type_header = Infrastructure : : Header : : from_string_pair ( " 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 ) ;
response - > header_list ( ) - > append ( move ( content_range_header ) ) ;
2023-08-01 18:53:39 -04:00
}
2025-01-19 19:02:18 +13:00
// 15. Return response.
2023-08-01 18:53:39 -04:00
return PendingResponse : : create ( vm , request , response ) ;
2022-10-23 22:16:14 +01:00
}
// -> "data"
else if ( request - > current_url ( ) . scheme ( ) = = " data " sv ) {
// 1. Let dataURLStruct be the result of running the data: URL processor on request’ s current URL.
2024-03-24 08:37:33 -04:00
auto data_url_struct = Infrastructure : : process_data_url ( request - > current_url ( ) ) ;
2022-10-23 22:16:14 +01:00
// 2. If dataURLStruct is failure, then return a network error.
AK: Decode data URLs to separate class (and parse like every other URL)
Parsing 'data:' URLs took it's own route. It never set standard URL
fields like path, query or fragment (except for scheme) and instead
gave us separate methods called `data_payload()`, `data_mime_type()`,
and `data_payload_is_base64()`.
Because parsing 'data:' didn't use standard fields, running the
following JS code:
new URL('#a', 'data:text/plain,hello').toString()
not only cleared the path as URLParser doesn't check for data from
data_payload() function (making the result be 'data:#a'), but it also
crashes the program because we forbid having an empty MIME type when we
serialize to string.
With this change, 'data:' URLs will be parsed like every other URLs.
To decode the 'data:' URL contents, one needs to call process_data_url()
on a URL, which will return a struct containing MIME type with already
decoded data! :^)
2023-07-06 19:11:58 +02:00
if ( data_url_struct . is_error ( ) )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Failed to process 'data:' URL " _string ) ) ;
2022-10-23 22:16:14 +01:00
// 3. Let mimeType be dataURLStruct’ s MIME type, serialized.
2024-10-14 11:06:43 +02:00
auto const & mime_type = data_url_struct . value ( ) . mime_type . serialized ( ) ;
2022-10-23 22:16:14 +01:00
// 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 ( ) ) ) ) ;
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Content-Type " sv , mime_type ) ;
response - > header_list ( ) - > append ( move ( header ) ) ;
2024-11-04 16:06:01 +01:00
response - > set_body ( Infrastructure : : byte_sequence_as_body ( realm , data_url_struct . value ( ) . body ) ) ;
2022-11-01 19:35:38 +00:00
return PendingResponse : : create ( vm , request , response ) ;
2022-10-23 22:16:14 +01:00
}
// -> "file"
2024-06-10 18:06:37 +02:00
// AD-HOC: "resource"
else if ( request - > current_url ( ) . scheme ( ) = = " file " sv | | request - > current_url ( ) . scheme ( ) = = " resource " sv ) {
2022-10-23 22:16:14 +01:00
// For now, unfortunate as it is, file: URLs are left as an exercise for the reader.
// When in doubt, return a network error.
2024-10-05 15:33:34 +13:00
if ( request - > origin ( ) . has < URL : : Origin > ( ) & & ( request - > origin ( ) . get < URL : : Origin > ( ) . is_opaque ( ) | | request - > origin ( ) . get < URL : : Origin > ( ) . scheme ( ) = = " file " sv | | request - > origin ( ) . get < URL : : Origin > ( ) . scheme ( ) = = " resource " sv ) )
2024-06-10 18:06:37 +02:00
return TRY ( nonstandard_resource_loader_file_or_http_network_fetch ( realm , fetch_params ) ) ;
else
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request with 'file:' or 'resource:' URL blocked " _string ) ) ;
2022-10-23 22:16:14 +01:00
}
// -> HTTP(S) scheme
else if ( Infrastructure : : is_http_or_https_scheme ( request - > current_url ( ) . scheme ( ) ) ) {
// Return the result of running HTTP fetch given fetchParams.
return http_fetch ( realm , fetch_params ) ;
}
// 4. Return a network error.
auto message = request - > current_url ( ) . scheme ( ) = = " about " sv
2023-08-07 11:12:38 +02:00
? " Request has invalid 'about:' URL, only 'about:blank' can be fetched " _string
: " Request URL has invalid scheme, must be one of 'about', 'blob', 'data', 'file', 'http', or 'https' " _string ;
2023-03-02 23:26:35 +00:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , move ( message ) ) ) ;
2022-10-23 22:16:14 +01:00
}
// https://fetch.spec.whatwg.org/#concept-http-fetch
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < PendingResponse > > http_fetch ( JS : : Realm & realm , Infrastructure : : FetchParams const & fetch_params , MakeCORSPreflight make_cors_preflight )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP fetch' with: fetch_params @ {}, make_cors_preflight = {} " ,
& fetch_params , make_cors_preflight = = MakeCORSPreflight : : Yes ? " Yes " sv : " No " sv ) ;
auto & vm = realm . vm ( ) ;
// 1. Let request be fetchParams’ s request.
auto request = fetch_params . request ( ) ;
2024-06-03 20:53:05 +01:00
// 2. Let response and internalResponse be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Infrastructure : : Response > response ;
GC : : Ptr < Infrastructure : : Response > internal_response ;
2022-10-23 22:16:14 +01:00
2024-06-03 20:53:05 +01:00
// 3. If request’ s service-workers mode is "all", then:
2022-10-23 22:16:14 +01:00
if ( request - > service_workers_mode ( ) = = Infrastructure : : Request : : ServiceWorkersMode : : All ) {
// 1. Let requestForServiceWorker be a clone of request.
2023-08-13 13:05:26 +02:00
auto request_for_service_worker = request - > clone ( realm ) ;
2022-10-23 22:16:14 +01:00
// 2. If requestForServiceWorker’ s body is non-null, then:
if ( ! request_for_service_worker - > body ( ) . has < Empty > ( ) ) {
// FIXME: 1. Let transformStream be a new TransformStream.
// FIXME: 2. Let transformAlgorithm given chunk be these steps:
// FIXME: 3. Set up transformStream with transformAlgorithm set to transformAlgorithm.
// FIXME: 4. Set requestForServiceWorker’ s body’ s stream to the result of requestForServiceWorker’ s body’ s stream
// piped through transformStream.
}
// 3. Let serviceWorkerStartTime be the coarsened shared current time given fetchParams’ s cross-origin isolated
// capability.
auto service_worker_start_time = HighResolutionTime : : coarsened_shared_current_time ( fetch_params . cross_origin_isolated_capability ( ) = = HTML : : CanUseCrossOriginIsolatedAPIs : : Yes ) ;
// FIXME: 4. Set response to the result of invoking handle fetch for requestForServiceWorker, with fetchParams’ s
// controller and fetchParams’ s cross-origin isolated capability.
2024-06-03 20:53:05 +01:00
// 5. If response is non-null, then:
2022-10-23 22:16:14 +01:00
if ( response ) {
// 1. Set fetchParams’ s timing info’ s final service worker start time to serviceWorkerStartTime.
fetch_params . timing_info ( ) - > set_final_service_worker_start_time ( service_worker_start_time ) ;
// 2. If request’ s body is non-null, then cancel request’ s body with undefined.
if ( ! request - > body ( ) . has < Empty > ( ) ) {
// FIXME: Implement cancelling streams
}
2024-06-03 20:53:05 +01:00
// 3. Set internalResponse to response, if response is not a filtered response; otherwise to response’ s
// internal response.
internal_response = ! is < Infrastructure : : FilteredResponse > ( * response )
2024-11-15 04:01:23 +13:00
? GC : : Ref { * response }
2022-10-23 22:16:14 +01:00
: static_cast < Infrastructure : : FilteredResponse const & > ( * response ) . internal_response ( ) ;
// 4. If one of the following is true
if (
// - response’ s type is "error"
response - > type ( ) = = Infrastructure : : Response : : Type : : Error
// - request’ s mode is "same-origin" and response’ s type is "cors"
| | ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : SameOrigin & & response - > type ( ) = = Infrastructure : : Response : : Type : : CORS )
// - request’ s mode is not "no-cors" and response’ s type is "opaque"
| | ( request - > mode ( ) ! = Infrastructure : : Request : : Mode : : NoCORS & & response - > type ( ) = = Infrastructure : : Response : : Type : : Opaque )
// - request’ s redirect mode is not "manual" and response’ s type is "opaqueredirect"
| | ( request - > redirect_mode ( ) ! = Infrastructure : : Request : : RedirectMode : : Manual & & response - > type ( ) = = Infrastructure : : Response : : Type : : OpaqueRedirect )
// - request’ s redirect mode is not "follow" and response’ s URL list has more than one item.
| | ( request - > redirect_mode ( ) ! = Infrastructure : : Request : : RedirectMode : : Follow & & response - > url_list ( ) . size ( ) > 1 ) ) {
// then return a network error.
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Invalid request/response state combination " _string ) ) ;
2022-10-23 22:16:14 +01:00
}
}
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < PendingResponse > pending_actual_response ;
2022-10-23 22:16:14 +01:00
2023-02-08 23:35:52 +00:00
auto returned_pending_response = PendingResponse : : create ( vm , request ) ;
2024-06-03 20:53:05 +01:00
// 4. If response is null, then:
2022-10-23 22:16:14 +01:00
if ( ! response ) {
// 1. If makeCORSPreflight is true and one of these conditions is true:
// NOTE: This step checks the CORS-preflight cache and if there is no suitable entry it performs a
// CORS-preflight fetch which, if successful, populates the cache. The purpose of the CORS-preflight
// fetch is to ensure the fetched resource is familiar with the CORS protocol. The cache is there to
// minimize the number of CORS-preflight fetches.
2024-11-15 04:01:23 +13:00
GC : : Ptr < PendingResponse > pending_preflight_response ;
2022-10-23 22:16:14 +01:00
if ( make_cors_preflight = = MakeCORSPreflight : : Yes & & (
2023-02-08 23:35:52 +00:00
// - There is no method cache entry match for request’ s method using request, and either request’ s
// method is not a CORS-safelisted method or request’ s use-CORS-preflight flag is set.
// FIXME: We currently have no cache, so there will always be no method cache entry.
( ! Infrastructure : : is_cors_safelisted_method ( request - > method ( ) ) | | request - > use_cors_preflight ( ) )
// - There is at least one item in the CORS-unsafe request-header names with request’ s header list for
// which there is no header-name cache entry match using request.
// FIXME: We currently have no cache, so there will always be no header-name cache entry.
2024-04-26 13:24:20 -04:00
| | ! Infrastructure : : get_cors_unsafe_header_names ( request - > header_list ( ) ) . is_empty ( ) ) ) {
2023-02-08 23:35:52 +00:00
// 1. Let preflightResponse be the result of running CORS-preflight fetch given request.
pending_preflight_response = TRY ( cors_preflight_fetch ( realm , request ) ) ;
// NOTE: Step 2 is performed in pending_preflight_response's load callback below.
2022-10-23 22:16:14 +01:00
}
2024-11-15 04:01:23 +13:00
auto fetch_main_content = [ request = GC : : make_root ( request ) , realm = GC : : make_root ( realm ) , fetch_params = GC : : make_root ( fetch_params ) ] ( ) - > WebIDL : : ExceptionOr < GC : : Ref < PendingResponse > > {
2023-02-08 23:35:52 +00:00
// 2. If request’ s redirect mode is "follow", then set request’ s service-workers mode to "none".
// NOTE: Redirects coming from the network (as opposed to from a service worker) are not to be exposed to a
// service worker.
if ( request - > redirect_mode ( ) = = Infrastructure : : Request : : RedirectMode : : Follow )
request - > set_service_workers_mode ( Infrastructure : : Request : : ServiceWorkersMode : : None ) ;
2024-06-03 20:53:05 +01:00
// 3. Set response and internalResponse to the result of running HTTP-network-or-cache fetch given fetchParams.
2023-02-08 23:35:52 +00:00
return http_network_or_cache_fetch ( * realm , * fetch_params ) ;
} ;
if ( pending_preflight_response ) {
pending_actual_response = PendingResponse : : create ( vm , request ) ;
2024-11-15 04:01:23 +13:00
pending_preflight_response - > when_loaded ( [ returned_pending_response , pending_actual_response , fetch_main_content = move ( fetch_main_content ) ] ( GC : : Ref < Infrastructure : : Response > preflight_response ) {
2023-02-08 23:35:52 +00:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP fetch' pending_preflight_response load callback " ) ;
2022-10-23 22:16:14 +01:00
2023-02-08 23:35:52 +00:00
// 2. If preflightResponse is a network error, then return preflightResponse.
if ( preflight_response - > is_network_error ( ) ) {
returned_pending_response - > resolve ( preflight_response ) ;
return ;
}
auto pending_main_content_response = TRY_OR_IGNORE ( fetch_main_content ( ) ) ;
2024-11-15 04:01:23 +13:00
pending_main_content_response - > when_loaded ( [ pending_actual_response ] ( GC : : Ref < Infrastructure : : Response > main_content_response ) {
2023-02-08 23:35:52 +00:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP fetch' pending_main_content_response load callback " ) ;
pending_actual_response - > resolve ( main_content_response ) ;
} ) ;
} ) ;
} else {
pending_actual_response = TRY ( fetch_main_content ( ) ) ;
}
2022-10-23 22:16:14 +01:00
} else {
2022-11-01 19:35:38 +00:00
pending_actual_response = PendingResponse : : create ( vm , request , Infrastructure : : Response : : create ( vm ) ) ;
2022-10-23 22:16:14 +01:00
}
2024-11-15 04:01:23 +13:00
pending_actual_response - > when_loaded ( [ & realm , & vm , & fetch_params , request , response , internal_response , returned_pending_response , response_was_null = ! response ] ( GC : : Ref < Infrastructure : : Response > resolved_actual_response ) mutable {
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP fetch' pending_actual_response load callback " ) ;
if ( response_was_null ) {
2024-06-03 20:53:05 +01:00
response = internal_response = resolved_actual_response ;
2022-10-23 22:16:14 +01:00
// 4. If request’ s response tainting is "cors" and a CORS check for request and response returns failure,
// then return a network error.
// NOTE: As the CORS check is not to be applied to responses whose status is 304 or 407, or responses from
// a service worker for that matter, it is applied here.
if ( request - > response_tainting ( ) = = Infrastructure : : Request : : ResponseTainting : : CORS
2024-04-26 13:35:10 -04:00
& & ! cors_check ( request , * response ) ) {
2023-08-07 11:12:38 +02:00
returned_pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , " Request with 'cors' response tainting failed CORS check " _string ) ) ;
2022-10-23 22:16:14 +01:00
return ;
}
// 5. If the TAO check for request and response returns failure, then set request’ s timing allow failed flag.
2024-04-26 13:35:10 -04:00
if ( ! tao_check ( request , * response ) )
2022-10-23 22:16:14 +01:00
request - > set_timing_allow_failed ( true ) ;
}
2024-06-03 20:53:05 +01:00
// 5. If either request’ s response tainting or response’ s type is "opaque", and the cross-origin resource
// policy check with request’ s origin, request’ s client, request’ s destination, and internalResponse returns
2022-10-23 22:16:14 +01:00
// blocked, then return a network error.
// NOTE: The cross-origin resource policy check runs for responses coming from the network and responses coming
// from the service worker. This is different from the CORS check, as request’ s client and the service
// worker can have different embedder policies.
if ( ( request - > response_tainting ( ) = = Infrastructure : : Request : : ResponseTainting : : Opaque | | response - > type ( ) = = Infrastructure : : Response : : Type : : Opaque )
& & false // FIXME: "and the cross-origin resource policy check with request’ s origin, request’ s client, request’ s destination, and actualResponse returns blocked"
) {
2023-08-07 11:12:38 +02:00
returned_pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , " Response was blocked by cross-origin resource policy check " _string ) ) ;
2022-10-23 22:16:14 +01:00
return ;
}
2024-11-15 04:01:23 +13:00
GC : : Ptr < PendingResponse > inner_pending_response ;
2022-10-23 22:16:14 +01:00
2024-06-03 20:53:05 +01:00
// 6. If internalResponse’ s status is a redirect status:
if ( Infrastructure : : is_redirect_status ( internal_response - > status ( ) ) ) {
// FIXME: 1. If internalResponse’ s status is not 303, request’ s body is non-null, and the connection uses HTTP/2,
2022-10-23 22:16:14 +01:00
// then user agents may, and are even encouraged to, transmit an RST_STREAM frame.
// NOTE: 303 is excluded as certain communities ascribe special status to it.
// 2. Switch on request’ s redirect mode:
switch ( request - > redirect_mode ( ) ) {
// -> "error"
case Infrastructure : : Request : : RedirectMode : : Error :
2024-06-03 20:53:05 +01:00
// 1. Set response to a network error.
2023-08-07 11:12:38 +02:00
response = Infrastructure : : Response : : network_error ( vm , " Request with 'error' redirect mode received redirect response " _string ) ;
2022-10-23 22:16:14 +01:00
break ;
// -> "manual"
case Infrastructure : : Request : : RedirectMode : : Manual :
2023-04-09 03:42:31 +03:00
// 1. If request’ s mode is "navigate", then set fetchParams’ s controller’ s next manual redirect steps
// to run HTTP-redirect fetch given fetchParams and response.
2022-10-23 22:16:14 +01:00
if ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : Navigate ) {
2022-11-19 01:09:53 +00:00
fetch_params . controller ( ) - > set_next_manual_redirect_steps ( [ & realm , & fetch_params , response ] {
2022-10-23 22:16:14 +01:00
( void ) http_redirect_fetch ( realm , fetch_params , * response ) ;
} ) ;
}
2023-04-09 03:42:31 +03:00
// 2. Otherwise, set response to an opaque-redirect filtered response whose internal response is
2024-06-03 20:53:05 +01:00
// internalResponse.
2023-04-09 03:42:31 +03:00
else {
2024-06-03 20:53:05 +01:00
response = Infrastructure : : OpaqueRedirectFilteredResponse : : create ( vm , * internal_response ) ;
2023-04-09 03:42:31 +03:00
}
2022-10-23 22:16:14 +01:00
break ;
// -> "follow"
case Infrastructure : : Request : : RedirectMode : : Follow :
2024-06-03 20:53:05 +01:00
// 1. Set response to the result of running HTTP-redirect fetch given fetchParams and response.
2022-10-23 22:16:14 +01:00
inner_pending_response = TRY_OR_IGNORE ( http_redirect_fetch ( realm , fetch_params , * response ) ) ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2024-04-07 15:40:20 +02:00
if ( inner_pending_response ) {
2024-11-15 04:01:23 +13:00
inner_pending_response - > when_loaded ( [ returned_pending_response ] ( GC : : Ref < Infrastructure : : Response > response ) {
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP fetch' inner_pending_response load callback " ) ;
returned_pending_response - > resolve ( response ) ;
} ) ;
} else {
returned_pending_response - > resolve ( * response ) ;
}
} ) ;
2024-06-03 20:53:05 +01:00
// 7. Return response.
// NOTE: Typically internalResponse’ s body’ s stream is still being enqueued to after returning.
2022-10-23 22:16:14 +01:00
return returned_pending_response ;
}
// https://fetch.spec.whatwg.org/#concept-http-redirect-fetch
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ptr < PendingResponse > > http_redirect_fetch ( JS : : Realm & realm , Infrastructure : : FetchParams const & fetch_params , Infrastructure : : Response & response )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP-redirect fetch' with: fetch_params @ {}, response = {} " , & fetch_params , & response ) ;
auto & vm = realm . vm ( ) ;
// 1. Let request be fetchParams’ s request.
auto request = fetch_params . request ( ) ;
2024-06-05 19:36:18 +01:00
// 2. Let internalResponse be response, if response is not a filtered response; otherwise response’ s internal
// response.
auto internal_response = ! is < Infrastructure : : FilteredResponse > ( response )
2024-11-15 04:01:23 +13:00
? GC : : Ref { response }
2022-10-23 22:16:14 +01:00
: static_cast < Infrastructure : : FilteredResponse const & > ( response ) . internal_response ( ) ;
2024-06-05 19:36:18 +01:00
// 3. Let locationURL be internalResponse’ s location URL given request’ s current URL’ s fragment.
auto location_url_or_error = internal_response - > location_url ( request - > current_url ( ) . fragment ( ) ) ;
2022-10-23 22:16:14 +01:00
// 4. If locationURL is null, then return response.
if ( ! location_url_or_error . is_error ( ) & & ! location_url_or_error . value ( ) . has_value ( ) )
2022-11-01 19:35:38 +00:00
return PendingResponse : : create ( vm , request , response ) ;
2022-10-23 22:16:14 +01:00
// 5. If locationURL is failure, then return a network error.
if ( location_url_or_error . is_error ( ) )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request redirect URL is invalid " _string ) ) ;
2022-10-23 22:16:14 +01:00
auto location_url = location_url_or_error . release_value ( ) . release_value ( ) ;
// 6. If locationURL’ s scheme is not an HTTP(S) scheme, then return a network error.
if ( ! Infrastructure : : is_http_or_https_scheme ( location_url . scheme ( ) ) )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request redirect URL must have HTTP or HTTPS scheme " _string ) ) ;
2022-10-23 22:16:14 +01:00
2022-12-07 18:48:40 +00:00
// 7. If request’ s redirect count is 20, then return a network error.
2022-10-23 22:16:14 +01:00
if ( request - > redirect_count ( ) = = 20 )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request has reached maximum redirect count of 20 " _string ) ) ;
2022-10-23 22:16:14 +01:00
2022-12-07 18:48:40 +00:00
// 8. Increase request’ s redirect count by 1.
2022-10-23 22:16:14 +01:00
request - > set_redirect_count ( request - > redirect_count ( ) + 1 ) ;
2024-06-05 19:36:18 +01:00
// 9. If request’ s mode is "cors", locationURL includes credentials, and request’ s origin is not same origin with
2022-10-23 22:16:14 +01:00
// locationURL’ s origin, then return a network error.
if ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : CORS
& & location_url . includes_credentials ( )
2024-10-05 15:33:34 +13:00
& & request - > origin ( ) . has < URL : : Origin > ( )
2024-10-05 17:03:51 +13:00
& & ! request - > origin ( ) . get < URL : : Origin > ( ) . is_same_origin ( location_url . origin ( ) ) ) {
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request with 'cors' mode and different URL and request origin must not include credentials in redirect URL " _string ) ) ;
2022-10-23 22:16:14 +01:00
}
// 10. If request’ s response tainting is "cors" and locationURL includes credentials, then return a network error.
// NOTE: This catches a cross-origin resource redirecting to a same-origin URL.
if ( request - > response_tainting ( ) = = Infrastructure : : Request : : ResponseTainting : : CORS & & location_url . includes_credentials ( ) )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request with 'cors' response tainting must not include credentials in redirect URL " _string ) ) ;
2022-10-23 22:16:14 +01:00
2024-06-05 19:36:18 +01:00
// 11. If internalResponse’ s status is not 303, request’ s body is non-null, and request’ s body’ s source is null, then
2022-10-23 22:16:14 +01:00
// return a network error.
2024-06-05 19:36:18 +01:00
if ( internal_response - > status ( ) ! = 303
2022-10-23 22:16:14 +01:00
& & ! request - > body ( ) . has < Empty > ( )
2024-11-15 04:01:23 +13:00
& & request - > body ( ) . get < GC : : Ref < Infrastructure : : Body > > ( ) - > source ( ) . has < Empty > ( ) ) {
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request has body but no body source " _string ) ) ;
2022-10-23 22:16:14 +01:00
}
// 12. If one of the following is true
if (
2024-06-05 19:36:18 +01:00
// - 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 ( ) )
// - 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 ( ) ) )
2022-10-23 22:16:14 +01:00
// 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_body ( { } ) ;
static constexpr Array request_body_header_names {
" Content-Encoding " sv ,
" Content-Language " sv ,
" Content-Location " sv ,
" Content-Type " sv
} ;
// 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 ( ) ) ;
}
2022-12-07 18:48:40 +00:00
// 13. If request’ s current URL’ s origin is not same origin with locationURL’ s origin, then for each headerName of
// CORS non-wildcard request-header name, delete headerName from request’ s header list.
// NOTE: I.e., the moment another origin is seen after the initial request, the `Authorization` header is removed.
2024-10-05 17:03:51 +13:00
if ( ! request - > current_url ( ) . origin ( ) . is_same_origin ( location_url . origin ( ) ) ) {
2022-12-07 18:48:40 +00:00
static constexpr Array cors_non_wildcard_request_header_names {
" Authorization " sv
} ;
for ( auto header_name : cors_non_wildcard_request_header_names )
request - > header_list ( ) - > delete_ ( header_name . bytes ( ) ) ;
}
// 14. If request’ s body is non-null, then set request’ s body to the body of the result of safely extracting
2022-10-23 22:16:14 +01:00
// request’ s body’ s source.
// NOTE: request’ s body’ s source’ s nullity has already been checked.
if ( ! request - > body ( ) . has < Empty > ( ) ) {
2024-11-15 04:01:23 +13:00
auto const & source = request - > body ( ) . get < GC : : Ref < Infrastructure : : Body > > ( ) - > source ( ) ;
2022-11-09 22:37:07 +00:00
// NOTE: BodyInitOrReadableBytes is a superset of Body::SourceType
2022-10-23 22:16:14 +01:00
auto converted_source = source . has < ByteBuffer > ( )
2022-11-09 22:37:07 +00:00
? BodyInitOrReadableBytes { source . get < ByteBuffer > ( ) }
2024-11-15 04:01:23 +13:00
: BodyInitOrReadableBytes { source . get < GC : : Root < FileAPI : : Blob > > ( ) } ;
2024-11-04 16:06:01 +01:00
auto [ body , _ ] = safely_extract_body ( realm , converted_source ) ;
request - > set_body ( body ) ;
2022-10-23 22:16:14 +01:00
}
2022-12-07 18:48:40 +00:00
// 15. Let timingInfo be fetchParams’ s timing info.
2022-10-23 22:16:14 +01:00
auto timing_info = fetch_params . timing_info ( ) ;
2022-12-07 18:48:40 +00:00
// 16. Set timingInfo’ s redirect end time and post-redirect start time to the coarsened shared current time given
2022-10-23 22:16:14 +01:00
// fetchParams’ s cross-origin isolated capability.
auto now = HighResolutionTime : : coarsened_shared_current_time ( fetch_params . cross_origin_isolated_capability ( ) = = HTML : : CanUseCrossOriginIsolatedAPIs : : Yes ) ;
timing_info - > set_redirect_end_time ( now ) ;
timing_info - > set_post_redirect_start_time ( now ) ;
2022-12-07 18:48:40 +00:00
// 17. If timingInfo’ s redirect start time is 0, then set timingInfo’ s redirect start time to timingInfo’ s start
2022-10-23 22:16:14 +01:00
// time.
if ( timing_info - > redirect_start_time ( ) = = 0 )
timing_info - > set_redirect_start_time ( timing_info - > start_time ( ) ) ;
2022-12-07 18:48:40 +00:00
// 18. Append locationURL to request’ s URL list.
2022-10-23 22:16:14 +01:00
request - > url_list ( ) . append ( location_url ) ;
2024-06-05 20:15:56 +01:00
// 19. Invoke set request’ s referrer policy on redirect on request and internalResponse.
ReferrerPolicy : : set_request_referrer_policy_on_redirect ( request , internal_response ) ;
2022-10-23 22:16:14 +01:00
2023-04-14 20:42:12 +03:00
// 20. Let recursive be true.
auto recursive = Recursive : : Yes ;
// 21. If request’ s redirect mode is "manual", then:
if ( request - > redirect_mode ( ) = = Infrastructure : : Request : : RedirectMode : : Manual ) {
// 1. Assert: request’ s mode is "navigate".
VERIFY ( request - > mode ( ) = = Infrastructure : : Request : : Mode : : Navigate ) ;
// 2. Set recursive to false.
recursive = Recursive : : No ;
}
// 22. Return the result of running main fetch given fetchParams and recursive.
return main_fetch ( realm , fetch_params , recursive ) ;
2022-10-23 22:16:14 +01:00
}
2024-06-22 18:53:11 +02:00
class CachePartition : public RefCounted < CachePartition > {
2024-05-21 10:38:10 -06:00
public :
2024-06-22 18:53:11 +02:00
// https://httpwg.org/specs/rfc9111.html#constructing.responses.from.caches
2024-12-26 14:58:27 +13:00
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
2024-05-21 10:38:10 -06:00
{
2024-06-22 18:53:11 +02:00
// When presented with a request, a cache MUST NOT reuse a stored response unless:
// - the presented target URI (Section 7.1 of [HTTP]) and that of the stored response match, and
2024-05-21 10:38:10 -06:00
auto it = m_cache . find ( url ) ;
2024-06-22 18:53:11 +02:00
if ( it = = m_cache . end ( ) ) {
dbgln ( " \033 [31;1mHTTP CACHE MISS! \033 [0m {} " , url ) ;
2024-05-21 10:38:10 -06:00
return { } ;
2024-06-22 18:53:11 +02:00
}
2024-10-22 11:47:22 +02:00
auto const & cached_response = it - > value ;
2024-05-21 10:38:10 -06:00
2024-06-22 18:53:11 +02:00
// - the request method associated with the stored response allows it to be used for the presented request, and
2024-10-22 11:47:22 +02:00
if ( method ! = cached_response - > method ( ) ) {
2024-06-22 18:53:11 +02:00
dbgln ( " \033 [31;1mHTTP CACHE MISS! \033 [0m (Bad method) {} " , url ) ;
return { } ;
}
2024-05-21 10:38:10 -06:00
2024-06-22 18:53:11 +02:00
// FIXME: - request header fields nominated by the stored response (if any) match those presented (see Section 4.1), and
2024-05-21 10:38:10 -06:00
( void ) headers ;
2024-06-22 18:53:11 +02:00
// FIXME: - the stored response does not contain the no-cache directive (Section 5.2.2.4), unless it is successfully validated (Section 4.3), and
2024-10-22 10:35:01 +02:00
2024-12-25 15:52:50 +01:00
initial_set_of_stored_responses . append ( * cached_response ) ;
2024-10-22 10:35:01 +02:00
// FIXME: - the stored response is one of the following:
2024-06-22 18:53:11 +02:00
// + fresh (see Section 4.2), or
// + allowed to be served stale (see Section 4.2.4), or
// + successfully validated (see Section 4.3).
dbgln ( " \033 [32;1mHTTP CACHE HIT! \033 [0m {} " , url ) ;
2024-12-26 14:58:27 +13:00
return cached_response - > clone ( realm ) ;
2024-06-22 18:53:11 +02:00
}
2024-10-22 11:47:22 +02:00
void store_response ( JS : : Realm & realm , Infrastructure : : Request const & http_request , Infrastructure : : Response const & response )
2024-06-22 18:53:11 +02:00
{
if ( ! is_cacheable ( http_request , response ) )
return ;
2024-10-22 11:47:22 +02:00
auto cached_response = Infrastructure : : Response : : create ( realm . vm ( ) ) ;
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_status ( response . status ( ) ) ;
cached_response - > url_list ( ) . append ( http_request . current_url ( ) ) ;
2024-06-22 18:53:11 +02:00
m_cache . set ( http_request . current_url ( ) , move ( cached_response ) ) ;
}
// https://httpwg.org/specs/rfc9111.html#freshening.responses
2024-11-15 04:01:23 +13:00
void freshen_stored_responses_upon_validation ( Infrastructure : : Response const & response , Vector < GC : : Ptr < Infrastructure : : Response > > & initial_set_of_stored_responses )
2024-06-22 18:53:11 +02:00
{
2024-10-22 10:35:01 +02:00
// When a cache receives a 304 (Not Modified) response, it needs to identify stored
// responses that are suitable for updating with the new information provided, and then do so.
// The initial set of stored responses to update are those that could have been
// chosen for that request — i.e., those that meet the requirements in Section 4,
// except the last requirement to be fresh, able to be served stale, or just validated.
2024-10-22 11:47:22 +02:00
for ( auto stored_response : initial_set_of_stored_responses ) {
// Then, that initial set of stored responses is further filtered by the first match of:
// - FIXME: If the new response contains one or more strong validators (see Section 8.8.1 of [HTTP]),
// then each of those strong validators identifies a selected representation for update.
// All the stored responses in the initial set with one of those same strong validators
// are identified for update.
// If none of the initial set contains at least one of the same strong validators,
// then the cache MUST NOT use the new response to update any stored responses.
// - FIXME: If the new response contains no strong validators but does contain one or more weak validators,
// and those validators correspond to one of the initial set's stored responses,
// then the most recent of those matching stored responses is identified for update.
// - FIXME: If the new response does not include any form of validator (such as where a client generates an
// `If-Modified-Since` request from a source other than the `Last-Modified` response header field),
// and there is only one stored response in the initial set, and that stored response also lacks a validator,
// then that stored response is identified for update.
// For each stored response identified, the cache MUST update its header fields
// with the header fields provided in the 304 (Not Modified) response, as per Section 3.2.
update_stored_header_fields ( response , stored_response - > header_list ( ) ) ;
}
2024-06-22 18:53:11 +02:00
}
2024-10-22 10:35:01 +02:00
private :
2024-06-22 18:53:11 +02:00
// https://httpwg.org/specs/rfc9111.html#storing.fields
2024-10-22 10:35:01 +02:00
bool is_exempted_for_storage ( StringView header_name )
2024-06-22 18:53:11 +02:00
{
// Caches MUST include all received response header fields — including unrecognized ones — when storing a response;
// this assures that new HTTP header fields can be successfully deployed. However, the following exceptions are made:
// - The Connection header field and fields whose names are listed in it are required by Section 7.6.1 of [HTTP]
// to be removed before forwarding the message. This MAY be implemented by doing so before storage.
// - Likewise, some fields' semantics require them to be removed before forwarding the message, and this MAY be
// implemented by doing so before storage; see Section 7.6.1 of [HTTP] for some examples.
// FIXME: - The no-cache (Section 5.2.2.4) and private (Section 5.2.2.7) cache directives can have arguments that
// prevent storage of header fields by all caches and shared caches, respectively.
// FIXME: - Header fields that are specific to the proxy that a cache uses when forwarding a request MUST NOT be stored,
// unless the cache incorporates the identity of the proxy into the cache key.
// Effectively, this is limited to Proxy-Authenticate (Section 11.7.1 of [HTTP]), Proxy-Authentication-Info (Section 11.7.3 of [HTTP]), and Proxy-Authorization (Section 11.7.2 of [HTTP]).
2024-10-22 10:35:01 +02:00
return header_name . is_one_of_ignoring_ascii_case (
" Connection " sv ,
" Proxy-Connection " sv ,
" Keep-Alive " sv ,
" TE " sv ,
" Transfer-Encoding " sv ,
" Upgrade " sv ) ;
}
// https://httpwg.org/specs/rfc9111.html#update
bool is_exempted_for_updating ( StringView header_name )
{
// Caches are required to update a stored response's header fields from another
// (typically newer) response in several situations; for example, see Sections 3.4, 4.3.4, and 4.3.5.
// When doing so, the cache MUST add each header field in the provided response to the stored response,
// replacing field values that are already present, with the following exceptions:
// - Header fields excepted from storage in Section 3.1,
return is_exempted_for_storage ( header_name )
// - Header fields that the cache's stored response depends upon, as described below,
| | false
// - Header fields that are automatically processed and removed by the recipient, as described below, and
| | false
// - The Content-Length header field.
| | header_name . equals_ignoring_ascii_case ( " Content-Length " sv ) ;
// In some cases, caches (especially in user agents) store the results of processing
// the received response, rather than the response itself, and updating header fields
// that affect that processing can result in inconsistent behavior and security issues.
// Caches in this situation MAY omit these header fields from updating stored responses
// on an exceptional basis but SHOULD limit such omission to those fields necessary to
// assure integrity of the stored response.
// For example, a browser might decode the content coding of a response while it is being received,
// creating a disconnect between the data it has stored and the response's original metadata.
// Updating that stored metadata with a different Content-Encoding header field would be problematic.
// Likewise, a browser might store a post-parse HTML tree rather than the content received in the response;
// updating the Content-Type header field would not be workable in this case because any assumptions about
// the format made in parsing would now be invalid.
// Furthermore, some fields are automatically processed and removed by the HTTP implementation,
// such as the Content-Range header field. Implementations MAY automatically omit such header fields from updates,
// even when the processing does not actually occur.
// Note that the Content-* prefix is not a signal that a header field is omitted from update; it is a convention for MIME header fields, not HTTP.
}
// https://httpwg.org/specs/rfc9111.html#update
2024-10-22 11:47:22 +02:00
void update_stored_header_fields ( Infrastructure : : Response const & response , Infrastructure : : HeaderList & headers )
2024-10-22 10:35:01 +02:00
{
2024-06-22 18:53:11 +02:00
for ( auto & header : * response . header_list ( ) ) {
auto name = StringView ( header . name ) ;
2024-10-22 11:47:22 +02:00
2024-10-22 10:35:01 +02:00
if ( is_exempted_for_updating ( name ) )
2024-06-22 18:53:11 +02:00
continue ;
2024-10-22 11:47:22 +02:00
headers . delete_ ( header . name ) ;
2024-10-22 10:35:01 +02:00
}
for ( auto & header : * response . header_list ( ) ) {
auto name = StringView ( header . name ) ;
2024-10-22 11:47:22 +02:00
2024-10-22 10:35:01 +02:00
if ( is_exempted_for_updating ( name ) )
continue ;
2024-10-22 11:47:22 +02:00
headers . append ( Infrastructure : : Header : : copy ( header ) ) ;
2024-10-22 10:35:01 +02:00
}
}
// https://httpwg.org/specs/rfc9111.html#storing.fields
2024-10-22 11:47:22 +02:00
void store_header_and_trailer_fields ( Infrastructure : : Response const & response , Web : : Fetch : : Infrastructure : : HeaderList & headers )
2024-10-22 10:35:01 +02:00
{
for ( auto & header : * response . header_list ( ) ) {
auto name = StringView ( header . name ) ;
if ( is_exempted_for_storage ( name ) )
continue ;
2024-10-22 11:47:22 +02:00
headers . append ( Infrastructure : : Header : : copy ( header ) ) ;
2024-06-22 18:53:11 +02:00
}
}
// https://httpwg.org/specs/rfc9111.html#response.cacheability
static bool is_cacheable ( Infrastructure : : Request const & request , Infrastructure : : Response const & response )
{
// A cache MUST NOT store a response to a request unless:
// - AD-HOC: For now, we simply don't cache responses without a simple ByteBuffer body.
if ( ! response . body ( ) | | ! response . body ( ) - > source ( ) . has < ByteBuffer > ( ) )
return false ;
// - the request method is understood by the cache;
if ( request . method ( ) ! = " GET " sv . bytes ( ) & & request . method ( ) ! = " HEAD " sv . bytes ( ) )
return false ;
// - the response status code is final (see Section 15 of [HTTP]);
if ( response . status ( ) < 200 )
return false ;
// - if the response status code is 206 or 304,
// or the must-understand cache directive (see Section 5.2.2.3) is present:
// the cache understands the response status code;
if ( response . status ( ) = = 206 | | response . status ( ) = = 304 ) {
// FIXME: Implement must-understand cache directive
}
// - the no-store cache directive is not present in the response (see Section 5.2.2.5);
if ( request . cache_mode ( ) = = Infrastructure : : Request : : CacheMode : : NoStore )
return false ;
// FIXME: - if the cache is shared: the private response directive is either not present
// or allows a shared cache to store a modified response; see Section 5.2.2.7);
// FIXME: - if the cache is shared: the Authorization header field is not present in the
// request (see Section 11.6.2 of [HTTP]) or a response directive is present
// that explicitly allows shared caching (see Section 3.5); and
// FIXME: - the response contains at least one of the following:
// + a public response directive (see Section 5.2.2.9);
// + a private response directive, if the cache is not shared (see Section 5.2.2.7);
// + an Expires header field (see Section 5.3);
// + a max-age response directive (see Section 5.2.2.1);
// + if the cache is shared: an s-maxage response directive (see Section 5.2.2.10);
// + a cache extension that allows it to be cached (see Section 5.2.3); or
// + a status code that is defined as heuristically cacheable (see Section 4.2.2).
return true ;
}
2024-12-25 15:52:50 +01:00
HashMap < URL : : URL , GC : : Root < Infrastructure : : Response > > m_cache ;
2024-05-21 10:38:10 -06:00
} ;
class HTTPCache {
public :
2024-05-31 18:44:49 +01:00
CachePartition & get ( Infrastructure : : NetworkPartitionKey const & key )
2024-05-21 10:38:10 -06:00
{
return * m_cache . ensure ( key , [ ] {
2024-06-22 18:53:11 +02:00
return adopt_ref ( * new CachePartition ) ;
2024-05-21 10:38:10 -06:00
} ) ;
}
static HTTPCache & the ( )
{
static HTTPCache s_cache ;
return s_cache ;
}
private :
2024-06-22 18:53:11 +02:00
HashMap < Infrastructure : : NetworkPartitionKey , NonnullRefPtr < CachePartition > > m_cache ;
2024-05-21 10:38:10 -06:00
} ;
// https://fetch.spec.whatwg.org/#determine-the-http-cache-partition
2024-06-22 18:53:11 +02:00
static RefPtr < CachePartition > determine_the_http_cache_partition ( Infrastructure : : Request const & request )
2024-05-21 10:38:10 -06:00
{
2024-06-22 18:53:11 +02:00
if ( ! g_http_cache_enabled )
return nullptr ;
2024-05-21 10:38:10 -06:00
// 1. Let key be the result of determining the network partition key given request.
2024-05-31 18:44:49 +01:00
auto key = Infrastructure : : determine_the_network_partition_key ( request ) ;
2024-05-21 10:38:10 -06:00
// 2. If key is null, then return null.
if ( ! key . has_value ( ) )
2024-06-22 18:53:11 +02:00
return nullptr ;
2024-05-21 10:38:10 -06:00
// 3. Return the unique HTTP cache associated with key. [HTTP-CACHING]
return HTTPCache : : the ( ) . get ( key . value ( ) ) ;
}
2022-10-23 22:16:14 +01:00
// https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < PendingResponse > > http_network_or_cache_fetch ( JS : : Realm & realm , Infrastructure : : FetchParams const & fetch_params , IsAuthenticationFetch is_authentication_fetch , IsNewConnectionFetch is_new_connection_fetch )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP-network-or-cache fetch' with: fetch_params @ {}, is_authentication_fetch = {}, is_new_connection_fetch = {} " ,
& fetch_params , is_authentication_fetch = = IsAuthenticationFetch : : Yes ? " Yes " sv : " No " sv , is_new_connection_fetch = = IsNewConnectionFetch : : Yes ? " Yes " sv : " No " sv ) ;
auto & vm = realm . vm ( ) ;
// 1. Let request be fetchParams’ s request.
auto request = fetch_params . request ( ) ;
// 2. Let httpFetchParams be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Infrastructure : : FetchParams const > http_fetch_params ;
2022-10-23 22:16:14 +01:00
// 3. Let httpRequest be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Infrastructure : : Request > http_request ;
2022-10-23 22:16:14 +01:00
// 4. Let response be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Infrastructure : : Response > response ;
2022-10-23 22:16:14 +01:00
// 5. Let storedResponse be null.
2024-11-15 04:01:23 +13:00
GC : : Ptr < Infrastructure : : Response > stored_response ;
2024-12-26 14:32:52 +01:00
GC : : RootVector < GC : : Ptr < Infrastructure : : Response > > initial_set_of_stored_responses ( realm . heap ( ) ) ;
2022-10-23 22:16:14 +01:00
// 6. Let httpCache be null.
// (Typeless until we actually implement it, needed for checks below)
2024-06-22 18:53:11 +02:00
RefPtr < CachePartition > http_cache ;
2022-10-23 22:16:14 +01:00
// 7. Let the revalidatingFlag be unset.
auto revalidating_flag = RefCountedFlag : : create ( false ) ;
auto include_credentials = IncludeCredentials : : No ;
// 8. Run these steps, but abort when fetchParams is canceled:
// NOTE: There's an 'if aborted' check after this anyway, so not doing this is fine and only incurs a small delay.
// For now, support for aborting fetch requests is limited anyway as ResourceLoader doesn't support it.
auto aborted = false ;
{
ScopeGuard set_aborted = [ & ] {
if ( fetch_params . is_canceled ( ) )
aborted = true ;
} ;
2025-08-07 10:54:40 +02:00
// 1. If request’ s traversable for user prompts is "no-traversable" and request’ s redirect mode is "error",
// then set httpFetchParams to fetchParams and httpRequest to request.
if ( request - > traversable_for_user_prompts ( ) . has < Infrastructure : : Request : : TraversableForUserPrompts > ( )
& & request - > traversable_for_user_prompts ( ) . get < Infrastructure : : Request : : TraversableForUserPrompts > ( ) = = Infrastructure : : Request : : TraversableForUserPrompts : : NoTraversable
2022-10-23 22:16:14 +01:00
& & request - > redirect_mode ( ) = = Infrastructure : : Request : : RedirectMode : : Error ) {
http_fetch_params = fetch_params ;
http_request = request ;
}
// 2. Otherwise:
else {
// 1. Set httpRequest to a clone of request.
// NOTE: Implementations are encouraged to avoid teeing request’ s body’ s stream when request’ s body’ s
// source is null as only a single body is needed in that case. E.g., when request’ s body’ s source
// is null, redirects and authentication will end up failing the fetch.
2023-08-13 13:05:26 +02:00
http_request = request - > clone ( realm ) ;
2022-10-23 22:16:14 +01:00
// 2. Set httpFetchParams to a copy of fetchParams.
// 3. Set httpFetchParams’ s request to httpRequest.
2023-02-25 10:44:51 -07:00
auto new_http_fetch_params = Infrastructure : : FetchParams : : create ( vm , * http_request , fetch_params . timing_info ( ) ) ;
new_http_fetch_params - > set_algorithms ( fetch_params . algorithms ( ) ) ;
new_http_fetch_params - > set_task_destination ( fetch_params . task_destination ( ) ) ;
new_http_fetch_params - > set_cross_origin_isolated_capability ( fetch_params . cross_origin_isolated_capability ( ) ) ;
new_http_fetch_params - > set_preloaded_response_candidate ( fetch_params . preloaded_response_candidate ( ) ) ;
http_fetch_params = new_http_fetch_params ;
2022-10-23 22:16:14 +01:00
}
// 3. Let includeCredentials be true if one of
if (
// - request’ s credentials mode is "include"
request - > credentials_mode ( ) = = Infrastructure : : Request : : CredentialsMode : : Include
// - request’ s credentials mode is "same-origin" and request’ s response tainting is "basic"
| | ( request - > credentials_mode ( ) = = Infrastructure : : Request : : CredentialsMode : : SameOrigin
& & request - > response_tainting ( ) = = Infrastructure : : Request : : ResponseTainting : : Basic )
// is true; otherwise false.
) {
include_credentials = IncludeCredentials : : Yes ;
} else {
include_credentials = IncludeCredentials : : No ;
}
// 4. If Cross-Origin-Embedder-Policy allows credentials with request returns false, then set
// includeCredentials to false.
if ( ! request - > cross_origin_embedder_policy_allows_credentials ( ) )
include_credentials = IncludeCredentials : : No ;
// 5. Let contentLength be httpRequest’ s body’ s length, if httpRequest’ s body is non-null; otherwise null.
2024-11-15 04:01:23 +13:00
auto content_length = http_request - > body ( ) . has < GC : : Ref < Infrastructure : : Body > > ( )
? http_request - > body ( ) . get < GC : : Ref < Infrastructure : : Body > > ( ) - > length ( )
2022-10-23 22:16:14 +01:00
: Optional < u64 > { } ;
// 6. Let contentLengthHeaderValue be null.
auto content_length_header_value = Optional < ByteBuffer > { } ;
// 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 ( ) ) ) ;
// 8. If contentLength is non-null, then set contentLengthHeaderValue to contentLength, serialized and
// isomorphic encoded.
if ( content_length . has_value ( ) )
2024-10-14 10:05:01 +02:00
content_length_header_value = MUST ( ByteBuffer : : copy ( String : : number ( * content_length ) . bytes ( ) ) ) ;
2022-10-23 22:16:14 +01:00
// 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 ( ) ,
} ;
2024-04-26 13:24:20 -04:00
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
2024-07-18 21:31:42 +01:00
// 10. If contentLength is non-null and httpRequest’ s keepalive is true, then:
2022-10-23 22:16:14 +01:00
if ( content_length . has_value ( ) & & http_request - > keepalive ( ) ) {
2024-07-18 21:31:42 +01:00
// 1. Let inflightKeepaliveBytes be 0.
u64 inflight_keep_alive_bytes = 0 ;
// 2. Let group be httpRequest’ s client’ s fetch group.
auto & group = http_request - > client ( ) - > fetch_group ( ) ;
// 3. Let inflightRecords be the set of fetch records in group whose request’ s keepalive is true and done flag is unset.
2025-07-29 21:23:30 +02:00
GC : : RootVector < GC : : Ref < Infrastructure : : FetchRecord > > in_flight_records ( vm . heap ( ) ) ;
for ( auto & fetch_record : group ) {
if ( fetch_record . request ( ) - > keepalive ( ) & & ! fetch_record . request ( ) - > done ( ) )
2024-07-18 21:31:42 +01:00
in_flight_records . append ( fetch_record ) ;
}
// 4. For each fetchRecord of inflightRecords:
for ( auto const & fetch_record : in_flight_records ) {
// 1. Let inflightRequest be fetchRecord’ s request.
auto const & in_flight_request = fetch_record - > request ( ) ;
// 2. Increment inflightKeepaliveBytes by inflightRequest’ s body’ s length.
inflight_keep_alive_bytes + = in_flight_request - > body ( ) . visit (
[ ] ( Empty ) - > u64 { return 0 ; } ,
[ ] ( ByteBuffer const & buffer ) - > u64 { return buffer . size ( ) ; } ,
2024-11-15 04:01:23 +13:00
[ ] ( GC : : Ref < Infrastructure : : Body > body ) - > u64 {
2024-07-18 21:31:42 +01:00
return body - > length ( ) . has_value ( ) ? body - > length ( ) . value ( ) : 0 ;
} ) ;
}
// 5. If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error.
2024-08-11 12:42:53 +01:00
if ( ( content_length . value ( ) + inflight_keep_alive_bytes ) > keepalive_maximum_size )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Keepalive request exceeded maximum allowed size of 64 KiB " _string ) ) ;
2024-07-18 21:31:42 +01:00
2022-10-23 22:16:14 +01:00
// NOTE: The above limit ensures that requests that are allowed to outlive the environment settings object
// and contain a body, have a bounded size and are not allowed to stay alive indefinitely.
}
// 11. If httpRequest’ s referrer is a URL, then:
2024-03-18 16:22:27 +13:00
if ( http_request - > referrer ( ) . has < URL : : URL > ( ) ) {
2022-10-23 22:16:14 +01:00
// 1. Let referrerValue be httpRequest’ s referrer, serialized and isomorphic encoded.
2024-04-03 21:51:34 -04:00
auto referrer_string = http_request - > referrer ( ) . get < URL : : URL > ( ) . serialize ( ) ;
auto referrer_value = TRY_OR_THROW_OOM ( vm , ByteBuffer : : copy ( referrer_string . bytes ( ) ) ) ;
2022-10-23 22:16:14 +01:00
// 2. Append (`Referer`, referrerValue) to httpRequest’ s header list.
auto header = Infrastructure : : Header {
. name = MUST ( ByteBuffer : : copy ( " Referer " sv . bytes ( ) ) ) ,
. value = move ( referrer_value ) ,
} ;
2024-04-26 13:24:20 -04:00
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
// 12. Append a request `Origin` header for httpRequest.
2024-04-26 13:35:10 -04:00
http_request - > add_origin_header ( ) ;
2022-10-23 22:16:14 +01:00
2024-04-29 20:57:34 +01:00
// 13. Append the Fetch metadata headers for httpRequest.
append_fetch_metadata_headers_for_request ( * http_request ) ;
2022-10-23 22:16:14 +01:00
2024-05-21 09:17:23 -06:00
// 14. FIXME If httpRequest’ s initiator is "prefetch", then set a structured field value
// given (`Sec-Purpose`, the token prefetch) in httpRequest’ s header list.
// 15. If httpRequest’ s header list does not contain `User-Agent`, then user agents should append
2022-10-23 22:16:14 +01:00
// (`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 ( ) ) ) ,
2023-12-27 11:43:14 +01:00
. value = Infrastructure : : default_user_agent_value ( ) ,
2022-10-23 22:16:14 +01:00
} ;
2024-04-26 13:24:20 -04:00
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
2024-05-21 09:17:23 -06:00
// 16. If httpRequest’ s cache mode is "default" and httpRequest’ s header list contains `If-Modified-Since`,
2022-10-23 22:16:14 +01:00
// `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 - > set_cache_mode ( Infrastructure : : Request : : CacheMode : : NoStore ) ;
}
2024-05-21 09:17:23 -06:00
// 17. If httpRequest’ s cache mode is "no-cache", httpRequest’ s prevent no-cache cache-control header
2022-10-23 22:16:14 +01:00
// modification flag is unset, and httpRequest’ s header list does not contain `Cache-Control`, then append
// (`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 ( ) ) ) {
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Cache-Control " sv , " max-age=0 " sv ) ;
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
2024-05-21 09:17:23 -06:00
// 18. If httpRequest’ s cache mode is "no-store" or "reload", then:
2022-10-23 22:16:14 +01:00
if ( http_request - > cache_mode ( ) = = Infrastructure : : Request : : CacheMode : : NoStore
| | 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 ( ) ) ) {
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Pragma " sv , " no-cache " sv ) ;
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
// 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 ( ) ) ) {
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Cache-Control " sv , " no-cache " sv ) ;
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
}
2024-05-21 09:17:23 -06:00
// 19. If httpRequest’ s header list contains `Range`, then append (`Accept-Encoding`, `identity`) to
2022-10-23 22:16:14 +01:00
// 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 ( ) ) ) {
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Accept-Encoding " sv , " identity " sv ) ;
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
2024-05-21 09:17:23 -06:00
// 20. Modify httpRequest’ s header list per HTTP. Do not append a given header if httpRequest’ s header list
2022-10-23 22:16:14 +01:00
// contains that header’ s name.
// NOTE: It would be great if we could make this more normative somehow. At this point headers such as
// `Accept-Encoding`, `Connection`, `DNT`, and `Host`, are to be appended if necessary.
// `Accept`, `Accept-Charset`, and `Accept-Language` must not be included at this point.
// NOTE: `Accept` and `Accept-Language` are already included (unless fetch() is used, which does not include
// the latter by default), and `Accept-Charset` is a waste of bytes. See HTTP header layer division for
// more details.
2025-04-02 09:30:34 -04:00
//
// 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 ) ;
2024-07-02 20:29:43 +01:00
http_request - > header_list ( ) - > append ( move ( header ) ) ;
}
2022-10-23 22:16:14 +01:00
2024-05-21 09:17:23 -06:00
// 21. If includeCredentials is true, then:
2022-10-23 22:16:14 +01:00
if ( include_credentials = = IncludeCredentials : : Yes ) {
// 1. If the user agent is not configured to block cookies for httpRequest (see section 7 of [COOKIES]),
// then:
if ( true ) {
// 1. Let cookies be the result of running the "cookie-string" algorithm (see section 5.4 of [COOKIES])
// with the user agent’ s cookie store and httpRequest’ s current URL.
auto cookies = ( [ & ] {
2025-09-07 15:31:25 +01:00
auto & page = Bindings : : principal_host_defined_page ( HTML : : principal_realm ( realm ) ) ;
return page . client ( ) . page_did_request_cookie ( http_request - > current_url ( ) , Cookie : : Source : : Http ) ;
2022-10-23 22:16:14 +01:00
} ) ( ) ;
// 2. If cookies is not the empty string, then append (`Cookie`, cookies) to httpRequest’ s header list.
if ( ! cookies . is_empty ( ) ) {
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Cookie " sv , cookies ) ;
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
}
// 2. If httpRequest’ s header list does not contain `Authorization`, then:
if ( ! http_request - > header_list ( ) - > contains ( " Authorization " sv . bytes ( ) ) ) {
// 1. Let authorizationValue be null.
2023-03-02 23:26:35 +00:00
auto authorization_value = Optional < String > { } ;
2022-10-23 22:16:14 +01:00
// 2. If there’ s an authentication entry for httpRequest and either httpRequest’ s use-URL-credentials
// flag is unset or httpRequest’ s current URL does not include credentials, then set
// authorizationValue to authentication entry.
if ( false // FIXME: "If there’ s an authentication entry for httpRequest"
& & ( ! http_request - > use_url_credentials ( ) | | ! http_request - > current_url ( ) . includes_credentials ( ) ) ) {
// FIXME: "set authorizationValue to authentication entry."
}
// 3. Otherwise, if httpRequest’ s current URL does include credentials and isAuthenticationFetch is
// true, set authorizationValue to httpRequest’ s current URL, converted to an `Authorization` value.
else if ( http_request - > current_url ( ) . includes_credentials ( ) & & is_authentication_fetch = = IsAuthenticationFetch : : Yes ) {
auto const & url = http_request - > current_url ( ) ;
2024-08-04 22:02:02 +12:00
auto payload = MUST ( String : : formatted ( " {}:{} " , URL : : percent_decode ( url . username ( ) ) , URL : : percent_decode ( url . password ( ) ) ) ) ;
2023-03-02 23:26:35 +00:00
authorization_value = TRY_OR_THROW_OOM ( vm , encode_base64 ( payload . bytes ( ) ) ) ;
2022-10-23 22:16:14 +01:00
}
// 4. If authorizationValue is non-null, then append (`Authorization`, authorizationValue) to
// httpRequest’ s header list.
if ( authorization_value . has_value ( ) ) {
2024-04-26 13:24:20 -04:00
auto header = Infrastructure : : Header : : from_string_pair ( " Authorization " sv , * authorization_value ) ;
http_request - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
}
}
2024-05-21 09:17:23 -06:00
// FIXME: 22. If there’ s a proxy-authentication entry, use it as appropriate.
2022-10-23 22:16:14 +01:00
// NOTE: This intentionally does not depend on httpRequest’ s credentials mode.
2024-05-21 10:38:10 -06:00
// 23. Set httpCache to the result of determining the HTTP cache partition, given httpRequest.
http_cache = determine_the_http_cache_partition ( * http_request ) ;
2022-10-23 22:16:14 +01:00
2024-05-21 09:17:23 -06:00
// 24. If httpCache is null, then set httpRequest’ s cache mode to "no-store".
2024-06-22 18:53:11 +02:00
if ( ! http_cache )
2022-10-23 22:16:14 +01:00
http_request - > set_cache_mode ( Infrastructure : : Request : : CacheMode : : NoStore ) ;
2024-05-21 09:17:23 -06:00
// 25. If httpRequest’ s cache mode is neither "no-store" nor "reload", then:
2022-10-23 22:16:14 +01:00
if ( http_request - > cache_mode ( ) ! = Infrastructure : : Request : : CacheMode : : NoStore
& & http_request - > cache_mode ( ) ! = Infrastructure : : Request : : CacheMode : : Reload ) {
2022-12-07 19:16:37 +00:00
// 1. Set storedResponse to the result of selecting a response from the httpCache, possibly needing
2022-10-23 22:16:14 +01:00
// validation, as per the "Constructing Responses from Caches" chapter of HTTP Caching [HTTP-CACHING],
// if any.
// NOTE: As mandated by HTTP, this still takes the `Vary` header into account.
2024-12-26 14:58:27 +13:00
stored_response = http_cache - > select_response ( realm , http_request - > current_url ( ) , http_request - > method ( ) , * http_request - > header_list ( ) , initial_set_of_stored_responses ) ;
2022-10-23 22:16:14 +01:00
// 2. If storedResponse is non-null, then:
2024-06-22 18:53:11 +02:00
if ( stored_response ) {
2024-05-21 20:16:45 -04:00
// 1. If cache mode is "default", storedResponse is a stale-while-revalidate response,
// and httpRequest’ s client is non-null, then:
if ( http_request - > cache_mode ( ) = = Infrastructure : : Request : : CacheMode : : Default
& & stored_response - > is_stale_while_revalidate ( )
& & http_request - > client ( ) ! = nullptr ) {
// 1. Set response to storedResponse.
response = stored_response ;
// 2. Set response’ s cache state to "local".
response - > set_cache_state ( Infrastructure : : Response : : CacheState : : Local ) ;
// 3. Let revalidateRequest be a clone of request.
auto revalidate_request = request - > clone ( realm ) ;
// 4. Set revalidateRequest’ s cache mode set to "no-cache".
revalidate_request - > set_cache_mode ( Infrastructure : : Request : : CacheMode : : NoCache ) ;
// 5. Set revalidateRequest’ s prevent no-cache cache-control header modification flag.
revalidate_request - > set_prevent_no_cache_cache_control_header_modification ( true ) ;
// 6. Set revalidateRequest’ s service-workers mode set to "none".
revalidate_request - > set_service_workers_mode ( Infrastructure : : Request : : ServiceWorkersMode : : None ) ;
// 7. In parallel, run main fetch given a new fetch params whose request is revalidateRequest.
2024-11-15 04:01:23 +13:00
Platform : : EventLoopPlugin : : the ( ) . deferred_invoke ( GC : : create_function ( realm . heap ( ) , [ & vm , & realm , revalidate_request , fetch_params = GC : : Ref ( fetch_params ) ] {
2024-05-21 20:16:45 -04:00
( void ) main_fetch ( realm , Infrastructure : : FetchParams : : create ( vm , revalidate_request , fetch_params - > timing_info ( ) ) ) ;
2024-10-31 02:39:29 +13:00
} ) ) ;
2024-05-21 20:16:45 -04:00
}
// 2. Otherwise:
else {
// 1. If storedResponse is a stale response, then set the revalidatingFlag.
if ( stored_response - > is_stale ( ) )
revalidating_flag - > set_value ( true ) ;
// 2. If the revalidatingFlag is set and httpRequest’ s cache mode is neither "force-cache" nor "only-if-cached", then:
if ( revalidating_flag - > value ( )
& & http_request - > cache_mode ( ) ! = Infrastructure : : Request : : CacheMode : : ForceCache
& & 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 ( ) ) {
2024-06-22 18:53:11 +02:00
http_request - > header_list ( ) - > append ( Infrastructure : : Header : : from_string_pair ( " If-None-Match " sv , * etag ) ) ;
2024-05-21 20:16:45 -04:00
}
// 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 ( ) ) {
2024-06-22 18:53:11 +02:00
http_request - > header_list ( ) - > append ( Infrastructure : : Header : : from_string_pair ( " If-Modified-Since " sv , * last_modified ) ) ;
2024-05-21 20:16:45 -04:00
}
}
// 3. Otherwise, set response to storedResponse and set response’ s cache state to "local".
else {
response = stored_response ;
response - > set_cache_state ( Infrastructure : : Response : : CacheState : : Local ) ;
}
}
2022-10-23 22:16:14 +01:00
}
}
}
// 9. If aborted, then return the appropriate network error for fetchParams.
if ( aborted )
2023-03-03 18:02:43 +00:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : appropriate_network_error ( vm , fetch_params ) ) ;
2022-10-23 22:16:14 +01:00
2024-11-15 04:01:23 +13:00
GC : : Ptr < PendingResponse > pending_forward_response ;
2022-10-23 22:16:14 +01:00
// 10. If response is null, then:
if ( ! response ) {
// 1. If httpRequest’ s cache mode is "only-if-cached", then return a network error.
if ( http_request - > cache_mode ( ) = = Infrastructure : : Request : : CacheMode : : OnlyIfCached )
2025-04-02 20:51:45 +13:00
return PendingResponse : : create ( vm , request , Infrastructure : : Response : : network_error ( vm , " Request with 'only-if-cached' cache mode doesn't have a cached response " _string ) ) ;
2022-10-23 22:16:14 +01:00
// 2. Let forwardResponse be the result of running HTTP-network fetch given httpFetchParams, includeCredentials,
// and isNewConnectionFetch.
2023-03-22 23:56:11 +01:00
pending_forward_response = TRY ( nonstandard_resource_loader_file_or_http_network_fetch ( realm , * http_fetch_params , include_credentials , is_new_connection_fetch ) ) ;
2022-10-23 22:16:14 +01:00
} else {
2022-11-01 19:35:38 +00:00
pending_forward_response = PendingResponse : : create ( vm , request , Infrastructure : : Response : : create ( vm ) ) ;
2022-10-23 22:16:14 +01:00
}
2022-11-01 19:35:38 +00:00
auto returned_pending_response = PendingResponse : : create ( vm , request ) ;
2022-10-23 22:16:14 +01:00
2024-11-15 04:01:23 +13:00
pending_forward_response - > when_loaded ( [ & realm , & vm , & fetch_params , request , response , stored_response , initial_set_of_stored_responses , http_request , returned_pending_response , is_authentication_fetch , is_new_connection_fetch , revalidating_flag , include_credentials , response_was_null = ! response , http_cache ] ( GC : : Ref < Infrastructure : : Response > resolved_forward_response ) mutable {
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP-network-or-cache fetch' pending_forward_response load callback " ) ;
if ( response_was_null ) {
auto forward_response = resolved_forward_response ;
// NOTE: TRACE is omitted as it is a forbidden method in Fetch.
2024-06-22 18:53:11 +02:00
auto method_is_unsafe = ! ( StringView { http_request - > method ( ) } . is_one_of ( " GET " sv , " HEAD " sv , " OPTIONS " sv ) ) ;
2022-10-23 22:16:14 +01:00
// 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
// Caching, and set storedResponse to null.
if ( method_is_unsafe & & forward_response - > status ( ) > = 200 & & forward_response - > status ( ) < = 399 ) {
// FIXME: "invalidate appropriate stored responses in httpCache, as per the "Invalidation" chapter of HTTP Caching"
stored_response = nullptr ;
}
// 4. If the revalidatingFlag is set and forwardResponse’ s status is 304, then:
if ( revalidating_flag - > value ( ) & & forward_response - > status ( ) = = 304 ) {
2024-06-22 18:53:11 +02:00
dbgln ( " \033 [34;1mHTTP CACHE REVALIDATE (304) \033 [0m {} " , http_request - > current_url ( ) ) ;
// 1. Update storedResponse’ s header list using forwardResponse’ s header list, as per the "Freshening
// Stored Responses upon Validation" chapter of HTTP Caching.
2022-10-23 22:16:14 +01:00
// NOTE: This updates the stored response in cache as well.
2024-10-22 11:47:22 +02:00
http_cache - > freshen_stored_responses_upon_validation ( * forward_response , initial_set_of_stored_responses ) ;
2022-10-23 22:16:14 +01:00
// 2. Set response to storedResponse.
response = stored_response ;
// 3. Set response’ s cache state to "validated".
if ( response )
response - > set_cache_state ( Infrastructure : : Response : : CacheState : : Validated ) ;
}
// 5. If response is null, then:
if ( ! response ) {
// 1. Set response to forwardResponse.
response = forward_response ;
2024-06-22 18:53:11 +02:00
// 2. Store httpRequest and forwardResponse in httpCache, as per the "Storing Responses in Caches" chapter of HTTP Caching.
2022-10-23 22:16:14 +01:00
// NOTE: If forwardResponse is a network error, this effectively caches the network error, which is
// sometimes known as "negative caching".
// NOTE: The associated body info is stored in the cache alongside the response.
2024-06-22 18:53:11 +02:00
if ( http_cache )
2024-10-22 11:47:22 +02:00
http_cache - > store_response ( realm , * http_request , * forward_response ) ;
2022-10-23 22:16:14 +01:00
}
}
// 11. Set response’ s URL list to a clone of httpRequest’ s URL list.
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 ( ) ) )
response - > set_range_requested ( true ) ;
// 13. Set response’ s request-includes-credentials to includeCredentials.
response - > set_request_includes_credentials ( include_credentials = = IncludeCredentials : : Yes ) ;
2022-11-01 19:35:38 +00:00
auto inner_pending_response = PendingResponse : : create ( vm , request , * response ) ;
2022-10-23 22:16:14 +01:00
// 14. If response’ s status is 401, httpRequest’ s response tainting is not "cors", includeCredentials is true,
2025-08-07 10:54:40 +02:00
// and request’ s traversable for user prompts is a traversable navigable:
2022-10-23 22:16:14 +01:00
if ( response - > status ( ) = = 401
& & http_request - > response_tainting ( ) ! = Infrastructure : : Request : : ResponseTainting : : CORS
& & include_credentials = = IncludeCredentials : : Yes
2025-08-07 10:54:40 +02:00
& & request - > traversable_for_user_prompts ( ) . has < GC : : Ptr < HTML : : TraversableNavigable > > ( )
2024-08-13 11:47:15 +02:00
// 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 ( ) ) ) {
2022-10-23 22:16:14 +01:00
// 1. Needs testing: multiple `WWW-Authenticate` headers, missing, parsing issues.
// (Red box in the spec, no-op)
// 2. If request’ s body is non-null, then:
if ( ! request - > body ( ) . has < Empty > ( ) ) {
// 1. If request’ s body’ s source is null, then return a network error.
2024-11-15 04:01:23 +13:00
if ( request - > body ( ) . get < GC : : Ref < Infrastructure : : Body > > ( ) - > source ( ) . has < Empty > ( ) ) {
2023-08-07 11:12:38 +02:00
returned_pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , " Request has body but no body source " _string ) ) ;
2022-10-23 22:16:14 +01:00
return ;
}
// 2. Set request’ s body to the body of the result of safely extracting request’ s body’ s source.
2024-11-15 04:01:23 +13:00
auto const & source = request - > body ( ) . get < GC : : Ref < Infrastructure : : Body > > ( ) - > source ( ) ;
2022-11-09 22:37:07 +00:00
// NOTE: BodyInitOrReadableBytes is a superset of Body::SourceType
2022-10-23 22:16:14 +01:00
auto converted_source = source . has < ByteBuffer > ( )
2022-11-09 22:37:07 +00:00
? BodyInitOrReadableBytes { source . get < ByteBuffer > ( ) }
2024-11-15 04:01:23 +13:00
: BodyInitOrReadableBytes { source . get < GC : : Root < FileAPI : : Blob > > ( ) } ;
2024-11-04 16:06:01 +01:00
auto [ body , _ ] = safely_extract_body ( realm , converted_source ) ;
request - > set_body ( body ) ;
2022-10-23 22:16:14 +01:00
}
// 3. If request’ s use-URL-credentials flag is unset or isAuthenticationFetch is true, then:
if ( ! request - > use_url_credentials ( ) | | is_authentication_fetch = = IsAuthenticationFetch : : Yes ) {
// 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
if ( fetch_params . is_canceled ( ) ) {
2023-03-03 18:02:43 +00:00
returned_pending_response - > resolve ( Infrastructure : : Response : : appropriate_network_error ( vm , fetch_params ) ) ;
2022-10-23 22:16:14 +01:00
return ;
}
// FIXME: 2. Let username and password be the result of prompting the end user for a username and password,
// respectively, in request’ s window.
dbgln ( " Fetch: Username/password prompt is not implemented, using empty strings. This request will probably fail. " ) ;
2023-12-16 17:49:34 +03:30
auto username = ByteString : : empty ( ) ;
auto password = ByteString : : empty ( ) ;
2022-10-23 22:16:14 +01:00
// 3. Set the username given request’ s current URL and username.
2024-08-10 13:12:19 +12:00
request - > current_url ( ) . set_username ( username ) ;
2022-10-23 22:16:14 +01:00
// 4. Set the password given request’ s current URL and password.
2024-08-10 13:12:19 +12:00
request - > current_url ( ) . set_password ( password ) ;
2022-10-23 22:16:14 +01:00
}
// 4. Set response to the result of running HTTP-network-or-cache fetch given fetchParams and true.
inner_pending_response = TRY_OR_IGNORE ( http_network_or_cache_fetch ( realm , fetch_params , IsAuthenticationFetch : : Yes ) ) ;
}
2024-11-15 04:01:23 +13:00
inner_pending_response - > when_loaded ( [ & realm , & vm , & fetch_params , request , returned_pending_response , is_authentication_fetch , is_new_connection_fetch ] ( GC : : Ref < Infrastructure : : Response > response ) {
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'HTTP network-or-cache fetch' inner_pending_response load callback " ) ;
// 15. If response’ s status is 407, then:
if ( response - > status ( ) = = 407 ) {
2025-08-07 10:54:40 +02:00
// 1. If request’ s traversable for user prompts is "no-traversable", then return a network error.
if ( request - > traversable_for_user_prompts ( ) . has < Infrastructure : : Request : : TraversableForUserPrompts > ( )
& & request - > traversable_for_user_prompts ( ) . get < Infrastructure : : Request : : TraversableForUserPrompts > ( ) = = Infrastructure : : Request : : TraversableForUserPrompts : : NoTraversable ) {
2023-08-07 11:12:38 +02:00
returned_pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , " Request requires proxy authentication but has 'no-window' set " _string ) ) ;
2022-10-23 22:16:14 +01:00
return ;
}
// 2. Needs testing: multiple `Proxy-Authenticate` headers, missing, parsing issues.
// (Red box in the spec, no-op)
// 3. If fetchParams is canceled, then return the appropriate network error for fetchParams.
if ( fetch_params . is_canceled ( ) ) {
2023-03-03 18:02:43 +00:00
returned_pending_response - > resolve ( Infrastructure : : Response : : appropriate_network_error ( vm , fetch_params ) ) ;
2022-10-23 22:16:14 +01:00
return ;
}
// FIXME: 4. Prompt the end user as appropriate in request’ s window and store the result as a
// proxy-authentication entry.
// NOTE: Remaining details surrounding proxy authentication are defined by HTTP.
// FIXME: 5. Set response to the result of running HTTP-network-or-cache fetch given fetchParams.
// (Doing this without step 4 would potentially lead to an infinite request cycle.)
}
2022-11-01 19:35:38 +00:00
auto inner_pending_response = PendingResponse : : create ( vm , request , * response ) ;
2022-10-23 22:16:14 +01:00
// 16. If all of the following are true
if (
// - response’ s status is 421
response - > status ( ) = = 421
// - isNewConnectionFetch is false
& & is_new_connection_fetch = = IsNewConnectionFetch : : No
// - request’ s body is null, or request’ s body is non-null and request’ s body’ s source is non-null
2024-11-15 04:01:23 +13:00
& & ( request - > body ( ) . has < Empty > ( ) | | ! request - > body ( ) . get < GC : : Ref < Infrastructure : : Body > > ( ) - > source ( ) . has < Empty > ( ) )
2022-10-23 22:16:14 +01:00
// then:
) {
// 1. If fetchParams is canceled, then return the appropriate network error for fetchParams.
if ( fetch_params . is_canceled ( ) ) {
2023-03-03 18:02:43 +00:00
returned_pending_response - > resolve ( Infrastructure : : Response : : appropriate_network_error ( vm , fetch_params ) ) ;
2022-10-23 22:16:14 +01:00
return ;
}
// 2. Set response to the result of running HTTP-network-or-cache fetch given fetchParams,
// isAuthenticationFetch, and true.
inner_pending_response = TRY_OR_IGNORE ( http_network_or_cache_fetch ( realm , fetch_params , is_authentication_fetch , IsNewConnectionFetch : : Yes ) ) ;
}
2024-11-15 04:01:23 +13:00
inner_pending_response - > when_loaded ( [ returned_pending_response , is_authentication_fetch ] ( GC : : Ref < Infrastructure : : Response > response ) {
2022-10-23 22:16:14 +01:00
// 17. If isAuthenticationFetch is true, then create an authentication entry for request and the given
// realm.
if ( is_authentication_fetch = = IsAuthenticationFetch : : Yes ) {
// FIXME: "create an authentication entry for request and the given realm"
}
returned_pending_response - > resolve ( response ) ;
} ) ;
} ) ;
} ) ;
// 18. Return response.
// NOTE: Typically response’ s body’ s stream is still being enqueued to after returning.
return returned_pending_response ;
}
# if defined(WEB_FETCH_DEBUG)
static void log_load_request ( auto const & load_request )
{
dbgln ( " Fetch: Invoking ResourceLoader " ) ;
dbgln ( " > {} {} HTTP/1.1 " , load_request . method ( ) , load_request . url ( ) ) ;
for ( auto const & [ name , value ] : load_request . headers ( ) )
dbgln ( " > {}: {} " , name , value ) ;
dbgln ( " > " ) ;
for ( auto line : StringView { load_request . body ( ) } . split_view ( ' \n ' , SplitBehavior : : KeepEmpty ) )
dbgln ( " > {} " , line ) ;
}
static void log_response ( auto const & status_code , auto const & headers , auto const & data )
{
dbgln ( " < HTTP/1.1 {} " , status_code . value_or ( 0 ) ) ;
2024-06-09 19:50:23 +02:00
for ( auto const & [ name , value ] : headers . headers ( ) )
2022-10-23 22:16:14 +01:00
dbgln ( " < {}: {} " , name , value ) ;
dbgln ( " < " ) ;
for ( auto line : StringView { data } . split_view ( ' \n ' , SplitBehavior : : KeepEmpty ) )
dbgln ( " < {} " , line ) ;
}
# endif
// https://fetch.spec.whatwg.org/#concept-http-network-fetch
// Drop-in replacement for 'HTTP-network fetch', but obviously non-standard :^)
2023-03-22 23:56:11 +01:00
// It also handles file:// URLs since those can also go through ResourceLoader.
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < PendingResponse > > nonstandard_resource_loader_file_or_http_network_fetch ( JS : : Realm & realm , Infrastructure : : FetchParams const & fetch_params , IncludeCredentials include_credentials , IsNewConnectionFetch is_new_connection_fetch )
2022-10-23 22:16:14 +01:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'non-standard HTTP-network fetch' with: fetch_params @ {} " , & fetch_params ) ;
2025-02-26 15:46:41 +00:00
auto fetch_timing_info = fetch_params . timing_info ( ) ;
auto cross_origin_isolated_capability = fetch_params . cross_origin_isolated_capability ( ) ;
2022-10-23 22:16:14 +01:00
auto & vm = realm . vm ( ) ;
( void ) include_credentials ;
( void ) is_new_connection_fetch ;
auto request = fetch_params . request ( ) ;
2024-11-25 10:37:28 +13:00
auto & page = Bindings : : principal_host_defined_page ( HTML : : principal_realm ( realm ) ) ;
2023-01-15 10:10:11 +01:00
2023-02-08 23:29:16 +00:00
// NOTE: Using LoadRequest::create_for_url_on_page here will unconditionally add cookies as long as there's a page available.
// However, it is up to http_network_or_cache_fetch to determine if cookies should be added to the request.
LoadRequest load_request ;
load_request . set_url ( request - > current_url ( ) ) ;
2023-12-15 20:33:16 +01:00
load_request . set_page ( page ) ;
2023-12-16 17:49:34 +03:30
load_request . set_method ( ByteString : : copy ( request - > method ( ) ) ) ;
2024-05-26 08:03:29 -04:00
2022-10-23 22:16:14 +01:00
for ( auto const & header : * request - > header_list ( ) )
2023-12-16 17:49:34 +03:30
load_request . set_header ( ByteString : : copy ( header . name ) , ByteString : : copy ( header . value ) ) ;
2024-05-26 08:03:29 -04:00
2024-11-15 04:01:23 +13:00
if ( auto const * body = request - > body ( ) . get_pointer < GC : : Ref < Infrastructure : : Body > > ( ) ) {
2023-08-18 19:38:13 +02:00
TRY ( ( * body ) - > source ( ) . visit (
2022-10-23 22:16:14 +01:00
[ & ] ( ByteBuffer const & byte_buffer ) - > WebIDL : : ExceptionOr < void > {
2023-01-07 12:14:54 -05:00
load_request . set_body ( TRY_OR_THROW_OOM ( vm , ByteBuffer : : copy ( byte_buffer ) ) ) ;
2022-10-23 22:16:14 +01:00
return { } ;
} ,
2024-11-15 04:01:23 +13:00
[ & ] ( GC : : Root < FileAPI : : Blob > const & blob_handle ) - > WebIDL : : ExceptionOr < void > {
2024-07-23 23:48:01 -07:00
load_request . set_body ( TRY_OR_THROW_OOM ( vm , ByteBuffer : : copy ( blob_handle - > raw_bytes ( ) ) ) ) ;
2022-10-23 22:16:14 +01:00
return { } ;
} ,
[ ] ( Empty ) - > WebIDL : : ExceptionOr < void > {
return { } ;
} ) ) ;
}
2022-11-01 19:35:38 +00:00
auto pending_response = PendingResponse : : create ( vm , request ) ;
2022-10-23 22:16:14 +01:00
2024-05-26 08:03:29 -04:00
if constexpr ( WEB_FETCH_DEBUG ) {
dbgln ( " Fetch: Invoking ResourceLoader " ) ;
2022-10-23 22:16:14 +01:00
log_load_request ( load_request ) ;
2024-05-26 08:03:29 -04:00
}
// FIXME: This check should be removed and all HTTP requests should go through the `ResourceLoader::load_unbuffered`
// path. The buffer option should then be supplied to the steps below that allow us to buffer data up to a
// user-agent-defined limit (or not). However, we will need to fully use stream operations throughout the
// fetch process to enable this (e.g. Body::fully_read must use streams for this to work).
if ( request - > buffer_policy ( ) = = Infrastructure : : Request : : BufferPolicy : : DoNotBufferResponse ) {
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-05-26 08:03:29 -04:00
2025-08-06 20:18:18 +02:00
// 10. Let stream be a new ReadableStream.
2024-11-14 05:50:17 +13:00
auto stream = realm . create < Streams : : ReadableStream > ( realm ) ;
auto fetched_data_receiver = realm . create < FetchedDataReceiver > ( fetch_params , stream ) ;
2024-05-26 08:03:29 -04:00
2025-08-06 20:18:18 +02:00
// 11. Let pullAlgorithm be the following steps:
2024-11-15 04:01:23 +13:00
auto pull_algorithm = GC : : create_function ( realm . heap ( ) , [ & realm , fetched_data_receiver ] ( ) {
2024-05-26 08:03:29 -04:00
// 1. Let promise be a new promise.
auto promise = WebIDL : : create_promise ( realm ) ;
// 2. Run the following steps in parallel:
// NOTE: This is handled by FetchedDataReceiver.
fetched_data_receiver - > set_pending_promise ( promise ) ;
// 3. Return promise.
return promise ;
} ) ;
2025-08-06 20:18:18 +02:00
// 12. Let cancelAlgorithm be an algorithm that aborts fetchParams’ s controller with reason, given reason.
2024-11-15 04:01:23 +13:00
auto cancel_algorithm = GC : : create_function ( realm . heap ( ) , [ & realm , & fetch_params ] ( JS : : Value reason ) {
2024-05-26 08:03:29 -04:00
fetch_params . controller ( ) - > abort ( realm , reason ) ;
return WebIDL : : create_resolved_promise ( realm , JS : : js_undefined ( ) ) ;
} ) ;
// 13. Set up stream with byte reading support with pullAlgorithm set to pullAlgorithm, cancelAlgorithm set to cancelAlgorithm.
2024-12-08 17:28:44 +13:00
stream - > set_up_with_byte_reading_support ( pull_algorithm , cancel_algorithm ) ;
2024-05-26 08:03:29 -04:00
2024-11-15 04:01:23 +13:00
auto on_headers_received = GC : : create_function ( vm . heap ( ) , [ & vm , request , pending_response , stream ] ( HTTP : : HeaderMap const & response_headers , Optional < u32 > status_code , Optional < String > const & reason_phrase ) {
2024-10-31 06:12:14 +13:00
( void ) request ;
2024-05-26 08:03:29 -04:00
if ( pending_response - > is_resolved ( ) ) {
// RequestServer will send us the response headers twice, the second time being for HTTP trailers. This
// fetch algorithm is not interested in trailers, so just drop them here.
return ;
}
auto response = Infrastructure : : Response : : create ( vm ) ;
response - > set_status ( status_code . value_or ( 200 ) ) ;
2024-10-23 16:46:26 -05:00
if ( reason_phrase . has_value ( ) )
response - > set_status_message ( MUST ( ByteBuffer : : copy ( reason_phrase . value ( ) . bytes ( ) ) ) ) ;
2024-05-26 08:03:29 -04:00
if constexpr ( WEB_FETCH_DEBUG ) {
dbgln ( " Fetch: ResourceLoader load for '{}' {}: (status {}) " ,
request - > url ( ) ,
Infrastructure : : is_ok_status ( response - > status ( ) ) ? " complete " sv : " failed " sv ,
response - > status ( ) ) ;
log_response ( status_code , response_headers , ReadonlyBytes { } ) ;
}
2024-06-09 11:28:37 +02:00
for ( auto const & [ name , value ] : response_headers . headers ( ) ) {
2024-12-12 10:26:41 -08:00
auto header = Infrastructure : : Header : : from_latin1_pair ( name , value ) ;
2024-05-26 08:03:29 -04:00
response - > header_list ( ) - > append ( move ( header ) ) ;
}
// 14. Set response’ s body to a new body whose stream is stream.
response - > set_body ( Infrastructure : : Body : : create ( vm , stream ) ) ;
// 17. Return response.
// NOTE: Typically response’ s body’ s stream is still being enqueued to after returning.
pending_response - > resolve ( response ) ;
2024-10-31 06:12:14 +13:00
} ) ;
2024-05-26 08:03:29 -04:00
// 16. Run these steps in parallel:
// FIXME: 1. Run these steps, but abort when fetchParams is canceled:
2024-11-15 04:01:23 +13:00
auto on_data_received = GC : : create_function ( vm . heap ( ) , [ fetched_data_receiver ] ( ReadonlyBytes bytes ) {
2024-05-26 08:03:29 -04:00
// 1. If one or more bytes have been transmitted from response’ s message body, then:
if ( ! bytes . is_empty ( ) ) {
// 1. Let bytes be the transmitted bytes.
// FIXME: 2. Let codings be the result of extracting header list values given `Content-Encoding` and response’ s header list.
// FIXME: 3. Increase response’ s body info’ s encoded size by bytes’ s length.
// FIXME: 4. Set bytes to the result of handling content codings given codings and bytes.
// FIXME: 5. Increase response’ s body info’ s decoded size by bytes’ s length.
// FIXME: 6. If bytes is failure, then terminate fetchParams’ s controller.
// 7. Append bytes to buffer.
fetched_data_receiver - > on_data_received ( bytes ) ;
// FIXME: 8. If the size of buffer is larger than an upper limit chosen by the user agent, ask the user agent
// to suspend the ongoing fetch.
}
2024-10-31 06:12:14 +13:00
} ) ;
2024-05-26 08:03:29 -04:00
2025-02-26 13:28:21 +00:00
auto on_complete = GC : : create_function ( vm . heap ( ) , [ & vm , & realm , pending_response , stream ] ( bool success , Requests : : RequestTimingInfo const & , Optional < StringView > error_message ) {
2025-02-26 15:46:41 +00:00
dbgln ( " FIXME: Implement on_complete timing info for unbuffered requests " ) ;
2024-10-24 20:39:18 +13:00
HTML : : TemporaryExecutionContext execution_context { realm , HTML : : TemporaryExecutionContext : : CallbacksEnabled : : Yes } ;
2024-05-26 08:03:29 -04:00
// 16.1.1.2. Otherwise, if the bytes transmission for response’ s message body is done normally and stream is readable,
// then close stream, and abort these in-parallel steps.
if ( success ) {
if ( stream - > is_readable ( ) )
stream - > close ( ) ;
}
// 16.1.2.2. Otherwise, if stream is readable, error stream with a TypeError.
else {
auto error = MUST ( String : : formatted ( " Load failed: {} " , error_message ) ) ;
2022-10-23 22:16:14 +01:00
2024-05-26 08:03:29 -04:00
if ( stream - > is_readable ( ) )
stream - > error ( JS : : TypeError : : create ( realm , error ) ) ;
if ( ! pending_response - > is_resolved ( ) )
pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , error ) ) ;
}
2024-10-31 06:12:14 +13:00
} ) ;
2024-05-26 08:03:29 -04:00
2024-10-31 06:12:14 +13:00
ResourceLoader : : the ( ) . load_unbuffered ( load_request , on_headers_received , on_data_received , on_complete ) ;
2024-05-26 08:03:29 -04:00
} else {
2025-02-26 15:46:41 +00:00
auto on_load_success = GC : : create_function ( vm . heap ( ) , [ & realm , & vm , request , pending_response , fetch_timing_info , cross_origin_isolated_capability ] ( ReadonlyBytes data , Requests : : RequestTimingInfo const & timing_info , HTTP : : HeaderMap const & response_headers , Optional < u32 > status_code , Optional < String > const & reason_phrase ) {
2024-10-31 06:12:14 +13:00
( void ) request ;
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: ResourceLoader load for '{}' complete " , request - > url ( ) ) ;
if constexpr ( WEB_FETCH_DEBUG )
log_response ( status_code , response_headers , data ) ;
2023-03-02 23:26:35 +00:00
auto [ body , _ ] = TRY_OR_IGNORE ( extract_body ( realm , data ) ) ;
2022-10-23 22:16:14 +01:00
auto response = Infrastructure : : Response : : create ( vm ) ;
response - > set_status ( status_code . value_or ( 200 ) ) ;
response - > set_body ( move ( body ) ) ;
2025-02-26 15:46:41 +00:00
auto body_info = response - > body_info ( ) ;
body_info . encoded_size = timing_info . encoded_body_size ;
body_info . decoded_size = data . size ( ) ;
response - > set_body_info ( body_info ) ;
2024-06-09 11:28:37 +02:00
for ( auto const & [ name , value ] : response_headers . headers ( ) ) {
2024-12-12 10:26:41 -08:00
auto header = Infrastructure : : Header : : from_latin1_pair ( name , value ) ;
2024-04-26 13:24:20 -04:00
response - > header_list ( ) - > append ( move ( header ) ) ;
2022-10-23 22:16:14 +01:00
}
2024-10-23 16:46:26 -05:00
if ( reason_phrase . has_value ( ) )
response - > set_status_message ( MUST ( ByteBuffer : : copy ( reason_phrase . value ( ) . bytes ( ) ) ) ) ;
2025-02-26 15:46:41 +00:00
fetch_timing_info - > update_final_timings ( timing_info , cross_origin_isolated_capability ) ;
2022-10-23 22:16:14 +01:00
pending_response - > resolve ( response ) ;
2024-10-31 06:12:14 +13:00
} ) ;
2024-05-26 08:03:29 -04:00
2025-02-26 15:46:41 +00:00
auto on_load_error = GC : : create_function ( vm . heap ( ) , [ & realm , & vm , request , pending_response , fetch_timing_info , cross_origin_isolated_capability ] ( ByteString const & error , Requests : : RequestTimingInfo const & timing_info , Optional < u32 > status_code , Optional < String > const & reason_phrase , ReadonlyBytes data , HTTP : : HeaderMap const & response_headers ) {
2024-10-31 06:12:14 +13:00
( void ) request ;
2022-10-23 22:16:14 +01:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: ResourceLoader load for '{}' failed: {} (status {}) " , request - > url ( ) , error , status_code . value_or ( 0 ) ) ;
2024-03-13 16:09:27 -04:00
if constexpr ( WEB_FETCH_DEBUG )
log_response ( status_code , response_headers , data ) ;
2022-10-23 22:16:14 +01:00
auto response = Infrastructure : : Response : : create ( vm ) ;
// FIXME: This is ugly, ResourceLoader should tell us.
if ( status_code . value_or ( 0 ) = = 0 ) {
2024-06-18 15:27:06 +02:00
response = Infrastructure : : Response : : network_error ( vm , TRY_OR_IGNORE ( String : : from_byte_string ( error ) ) ) ;
2022-10-23 22:16:14 +01:00
} else {
2023-04-04 16:10:19 -04:00
response - > set_type ( Infrastructure : : Response : : Type : : Error ) ;
response - > set_status ( status_code . value_or ( 400 ) ) ;
2023-10-02 19:29:15 +02:00
auto [ body , _ ] = TRY_OR_IGNORE ( extract_body ( realm , data ) ) ;
2025-04-20 19:06:10 -04:00
response - > set_body ( move ( body ) ) ;
2025-02-26 15:46:41 +00:00
auto body_info = response - > body_info ( ) ;
body_info . encoded_size = timing_info . encoded_body_size ;
body_info . decoded_size = data . size ( ) ;
response - > set_body_info ( body_info ) ;
2024-06-09 11:28:37 +02:00
for ( auto const & [ name , value ] : response_headers . headers ( ) ) {
2024-12-12 10:26:41 -08:00
auto header = Infrastructure : : Header : : from_latin1_pair ( name , value ) ;
2024-04-26 13:24:20 -04:00
response - > header_list ( ) - > append ( move ( header ) ) ;
2023-10-02 19:29:15 +02:00
}
2024-10-23 16:46:26 -05:00
if ( reason_phrase . has_value ( ) )
response - > set_status_message ( MUST ( ByteBuffer : : copy ( reason_phrase . value ( ) . bytes ( ) ) ) ) ;
2022-10-23 22:16:14 +01:00
}
2025-02-26 15:46:41 +00:00
fetch_timing_info - > update_final_timings ( timing_info , cross_origin_isolated_capability ) ;
2022-10-23 22:16:14 +01:00
pending_response - > resolve ( response ) ;
2024-10-31 06:12:14 +13:00
} ) ;
2024-05-26 08:03:29 -04:00
2024-10-31 06:12:14 +13:00
ResourceLoader : : the ( ) . load ( load_request , on_load_success , on_load_error ) ;
2024-05-26 08:03:29 -04:00
}
2022-10-23 22:16:14 +01:00
return pending_response ;
}
2023-02-08 23:35:52 +00:00
// https://fetch.spec.whatwg.org/#cors-preflight-fetch-0
2024-11-15 04:01:23 +13:00
WebIDL : : ExceptionOr < GC : : Ref < PendingResponse > > cors_preflight_fetch ( JS : : Realm & realm , Infrastructure : : Request & request )
2023-02-08 23:35:52 +00:00
{
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'CORS-preflight fetch' with request @ {} " , & request ) ;
auto & vm = realm . vm ( ) ;
// 1. Let preflight be a new request whose method is `OPTIONS`, URL list is a clone of request’ s URL list, initiator is
// 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 ( TRY_OR_THROW_OOM ( vm , ByteBuffer : : copy ( " OPTIONS " sv . bytes ( ) ) ) ) ;
preflight - > set_url_list ( request . url_list ( ) ) ;
preflight - > set_initiator ( request . initiator ( ) ) ;
preflight - > set_destination ( request . destination ( ) ) ;
preflight - > set_origin ( request . origin ( ) ) ;
preflight - > set_referrer ( request . referrer ( ) ) ;
preflight - > set_referrer_policy ( request . referrer_policy ( ) ) ;
preflight - > set_mode ( Infrastructure : : Request : : Mode : : CORS ) ;
preflight - > set_response_tainting ( Infrastructure : : Request : : ResponseTainting : : CORS ) ;
// 2. Append (`Accept`, `*/*`) to preflight’ s header list.
2024-04-26 13:24:20 -04:00
auto temp_header = Infrastructure : : Header : : from_string_pair ( " Accept " sv , " */* " sv ) ;
preflight - > header_list ( ) - > append ( move ( temp_header ) ) ;
2023-02-08 23:35:52 +00:00
// 3. Append (`Access-Control-Request-Method`, request’ s method) to preflight’ s header list.
2024-04-26 13:24:20 -04:00
temp_header = Infrastructure : : Header : : from_string_pair ( " Access-Control-Request-Method " sv , request . method ( ) ) ;
preflight - > header_list ( ) - > append ( move ( temp_header ) ) ;
2023-02-08 23:35:52 +00:00
// 4. Let headers be the CORS-unsafe request-header names with request’ s header list.
2024-04-26 13:24:20 -04:00
auto headers = Infrastructure : : get_cors_unsafe_header_names ( request . header_list ( ) ) ;
2023-02-08 23:35:52 +00:00
// 5. If headers is not empty, then:
if ( ! headers . is_empty ( ) ) {
// 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 )
TRY_OR_THROW_OOM ( vm , value . try_append ( ' , ' ) ) ;
TRY_OR_THROW_OOM ( vm , value . try_append ( header ) ) ;
first = false ;
}
// 2. Append (`Access-Control-Request-Headers`, value) to preflight’ s header list.
temp_header = Infrastructure : : Header {
. name = TRY_OR_THROW_OOM ( vm , ByteBuffer : : copy ( " Access-Control-Request-Headers " sv . bytes ( ) ) ) ,
. value = move ( value ) ,
} ;
2024-04-26 13:24:20 -04:00
preflight - > header_list ( ) - > append ( move ( temp_header ) ) ;
2023-02-08 23:35:52 +00:00
}
// 6. Let response be the result of running HTTP-network-or-cache fetch given a new fetch params whose request is preflight.
// FIXME: The spec doesn't say anything about timing_info here, but FetchParams requires a non-null FetchTimingInfo object.
auto timing_info = Infrastructure : : FetchTimingInfo : : create ( vm ) ;
auto fetch_params = Infrastructure : : FetchParams : : create ( vm , preflight , timing_info ) ;
auto returned_pending_response = PendingResponse : : create ( vm , request ) ;
auto preflight_response = TRY ( http_network_or_cache_fetch ( realm , fetch_params ) ) ;
2024-11-15 04:01:23 +13:00
preflight_response - > when_loaded ( [ & vm , & request , returned_pending_response ] ( GC : : Ref < Infrastructure : : Response > response ) {
2023-02-08 23:35:52 +00:00
dbgln_if ( WEB_FETCH_DEBUG , " Fetch: Running 'CORS-preflight fetch' preflight_response load callback " ) ;
// 7. If a CORS check for request and response returns success and response’ s status is an ok status, then:
// NOTE: The CORS check is done on request rather than preflight to ensure the correct credentials mode is used.
2024-04-26 13:35:10 -04:00
if ( cors_check ( request , response ) & & Infrastructure : : is_ok_status ( response - > status ( ) ) ) {
2023-02-08 23:35:52 +00:00
// 1. Let methods be the result of extracting header list values given `Access-Control-Allow-Methods` and response’ s header list.
2024-04-26 13:24:20 -04:00
auto methods_or_failure = Infrastructure : : extract_header_list_values ( " Access-Control-Allow-Methods " sv . bytes ( ) , response - > header_list ( ) ) ;
2023-02-08 23:35:52 +00:00
// 2. Let headerNames be the result of extracting header list values given `Access-Control-Allow-Headers` and
// response’ s header list.
2024-04-26 13:24:20 -04:00
auto header_names_or_failure = Infrastructure : : extract_header_list_values ( " Access-Control-Allow-Headers " sv . bytes ( ) , response - > header_list ( ) ) ;
2023-02-08 23:35:52 +00:00
// 3. If either methods or headerNames is failure, return a network error.
if ( methods_or_failure . has < Infrastructure : : ExtractHeaderParseFailure > ( ) ) {
2023-08-07 11:12:38 +02:00
returned_pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , " The Access-Control-Allow-Methods in the CORS-preflight response is syntactically invalid " _string ) ) ;
2023-02-08 23:35:52 +00:00
return ;
}
if ( header_names_or_failure . has < Infrastructure : : ExtractHeaderParseFailure > ( ) ) {
2023-08-07 11:12:38 +02:00
returned_pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , " The Access-Control-Allow-Headers in the CORS-preflight response is syntactically invalid " _string ) ) ;
2023-02-08 23:35:52 +00:00
return ;
}
// NOTE: We treat "methods_or_failure" being `Empty` as empty Vector here.
auto methods = methods_or_failure . has < Vector < ByteBuffer > > ( ) ? methods_or_failure . get < Vector < ByteBuffer > > ( ) : Vector < ByteBuffer > { } ;
// NOTE: We treat "header_names_or_failure" being `Empty` as empty Vector here.
auto header_names = header_names_or_failure . has < Vector < ByteBuffer > > ( ) ? header_names_or_failure . get < Vector < ByteBuffer > > ( ) : Vector < ByteBuffer > { } ;
// 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 ( ) ) ) } ;
// 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 ) {
2025-04-08 13:50:50 -04:00
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 ( ) } ) ) ) ) ;
2023-02-08 23:35:52 +00:00
return ;
}
if ( ! methods . contains_slow ( " * " sv . bytes ( ) ) ) {
2025-04-08 13:50:50 -04:00
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 ( ) } ) ) ) ) ;
2023-02-08 23:35:52 +00:00
return ;
}
}
// 6. If one of request’ s header list’ s names is a CORS non-wildcard request-header name and is not a byte-case-insensitive match
// for an item in headerNames, then return a network error.
for ( auto const & header : * request . header_list ( ) ) {
if ( Infrastructure : : is_cors_non_wildcard_request_header_name ( header . name ) ) {
bool is_in_header_names = false ;
for ( auto const & allowed_header_name : header_names ) {
2023-03-10 08:48:54 +01:00
if ( StringView { allowed_header_name } . equals_ignoring_ascii_case ( header . name ) ) {
2023-02-08 23:35:52 +00:00
is_in_header_names = true ;
break ;
}
}
if ( ! is_in_header_names ) {
2025-04-08 13:50:50 -04:00
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 } ) ) ) ) ;
2023-02-08 23:35:52 +00:00
return ;
}
}
}
// 7. For each unsafeName of the CORS-unsafe request-header names with request’ s header list, if unsafeName is not a
// byte-case-insensitive match for an item in headerNames and request’ s credentials mode is "include" or headerNames
// does not contain `*`, return a network error.
2024-04-26 13:24:20 -04:00
auto unsafe_names = Infrastructure : : get_cors_unsafe_header_names ( request . header_list ( ) ) ;
2023-02-08 23:35:52 +00:00
for ( auto const & unsafe_name : unsafe_names ) {
bool is_in_header_names = false ;
for ( auto const & header_name : header_names ) {
2023-03-10 08:48:54 +01:00
if ( StringView { unsafe_name } . equals_ignoring_ascii_case ( header_name ) ) {
2023-02-08 23:35:52 +00:00
is_in_header_names = true ;
break ;
}
}
if ( ! is_in_header_names ) {
if ( request . credentials_mode ( ) = = Infrastructure : : Request : : CredentialsMode : : Include ) {
2025-04-08 13:50:50 -04:00
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 } ) ) ) ) ;
2023-02-08 23:35:52 +00:00
return ;
}
if ( ! header_names . contains_slow ( " * " sv . bytes ( ) ) ) {
2025-04-08 13:50:50 -04:00
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 } ) ) ) ) ;
2023-02-08 23:35:52 +00:00
return ;
}
}
}
// FIXME: 8. Let max-age be the result of extracting header list values given `Access-Control-Max-Age` and response’ s header list.
// FIXME: 9. If max-age is failure or null, then set max-age to 5.
// FIXME: 10. If max-age is greater than an imposed limit on max-age, then set max-age to the imposed limit.
// 11. If the user agent does not provide for a cache, then return response.
// NOTE: Since we don't currently have a cache, this is always true.
returned_pending_response - > resolve ( response ) ;
return ;
// FIXME: 12. For each method in methods for which there is a method cache entry match using request, set matching entry’ s max-age
// to max-age.
// FIXME: 13. For each method in methods for which there is no method cache entry match using request, create a new cache entry
// with request, max-age, method, and null.
// FIXME: 14. For each headerName in headerNames for which there is a header-name cache entry match using request, set matching
// entry’ s max-age to max-age.
// FIXME: 15. For each headerName in headerNames for which there is no header-name cache entry match using request, create a
// new cache entry with request, max-age, null, and headerName.
// FIXME: 16. Return response.
}
// 8. Otherwise, return a network error.
2023-08-07 11:12:38 +02:00
returned_pending_response - > resolve ( Infrastructure : : Response : : network_error ( vm , " CORS-preflight check failed " _string ) ) ;
2023-02-08 23:35:52 +00:00
} ) ;
return returned_pending_response ;
}
2024-04-29 20:54:14 +01:00
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-dest
void set_sec_fetch_dest_header ( Infrastructure : : Request & request )
{
// 1. Assert: r’ s url is a potentially trustworthy URL.
VERIFY ( SecureContexts : : is_url_potentially_trustworthy ( request . url ( ) ) = = SecureContexts : : Trustworthiness : : PotentiallyTrustworthy ) ;
// 2. Let header be a Structured Header whose value is a token.
// 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 ( ) ) ) ;
}
// 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 ) ) ;
}
2024-04-29 20:55:44 +01:00
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-dest
void set_sec_fetch_mode_header ( Infrastructure : : Request & request )
{
// 1. Assert: r’ s url is a potentially trustworthy URL.
VERIFY ( SecureContexts : : is_url_potentially_trustworthy ( request . url ( ) ) = = SecureContexts : : Trustworthiness : : PotentiallyTrustworthy ) ;
// 2. Let header be a Structured Header whose value is a token.
// 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 ( ) ) ) ;
// 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 ) ) ;
}
2024-04-29 20:56:30 +01:00
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-site
void set_sec_fetch_site_header ( Infrastructure : : Request & request )
{
// 1. Assert: r’ s url is a potentially trustworthy URL.
VERIFY ( SecureContexts : : is_url_potentially_trustworthy ( request . url ( ) ) = = SecureContexts : : Trustworthiness : : PotentiallyTrustworthy ) ;
// 2. Let header be a Structured Header whose value is a token.
// 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 ;
// 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 ) ) {
2025-01-28 19:10:32 +00:00
VERIFY ( request . origin ( ) . has < URL : : Origin > ( ) ) ;
auto & request_origin = request . origin ( ) . get < URL : : Origin > ( ) ;
2024-04-29 20:56:30 +01:00
for ( auto & url : request . url_list ( ) ) {
// 1. If url is same origin with r’ s origin, continue.
2025-01-28 19:10:32 +00:00
if ( url . origin ( ) . is_same_origin ( request_origin ) )
2024-04-29 20:56:30 +01:00
continue ;
// 2. Set header’ s value to cross-site.
header_value = " cross-site " sv ;
2025-01-28 19:10:32 +00:00
// 3. If r’ s origin is not same site with url’ s origin, then break.
if ( ! request_origin . is_same_site ( url . origin ( ) ) )
break ;
2024-04-29 20:56:30 +01:00
2025-01-28 19:10:32 +00:00
// 4. Set header’ s value to same-site.
header_value = " same-site " sv ;
2024-04-29 20:56:30 +01:00
}
}
// 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 ) ) ;
}
2024-04-29 20:56:57 +01:00
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-user
void set_sec_fetch_user_header ( Infrastructure : : Request & request )
{
// 1. Assert: r’ s url is a potentially trustworthy URL.
VERIFY ( SecureContexts : : is_url_potentially_trustworthy ( request . url ( ) ) = = SecureContexts : : Trustworthiness : : PotentiallyTrustworthy ) ;
// 2. If r is not a navigation request, or if r’ s user-activation is false, return.
if ( ! request . is_navigation_request ( ) | | ! request . user_activation ( ) )
return ;
// 3. Let header be a Structured Header whose value is a token.
// FIXME: This is handled below, as Serenity doesn't have APIs for RFC 8941.
// 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 ( ) ) ) ;
// 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 ) ) ;
}
2024-04-29 20:57:34 +01:00
// https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-append-the-fetch-metadata-headers-for-a-request
void append_fetch_metadata_headers_for_request ( Infrastructure : : Request & request )
{
// 1. If r’ s url is not an potentially trustworthy URL, return.
if ( SecureContexts : : is_url_potentially_trustworthy ( request . url ( ) ) ! = SecureContexts : : Trustworthiness : : PotentiallyTrustworthy )
return ;
// 2. Set the Sec-Fetch-Dest header for r.
set_sec_fetch_dest_header ( request ) ;
// 3. Set the Sec-Fetch-Mode header for r.
set_sec_fetch_mode_header ( request ) ;
// 4. Set the Sec-Fetch-Site header for r.
set_sec_fetch_site_header ( request ) ;
// 5. Set the Sec-Fetch-User header for r.
set_sec_fetch_user_header ( request ) ;
}
2025-06-28 03:32:38 -07:00
void set_http_cache_enabled ( bool const enabled )
{
g_http_cache_enabled = enabled ;
}
2022-10-23 22:16:14 +01:00
}