Skip to main content

net/
http_loader.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cmp::min;
6use std::collections::HashSet;
7use std::iter::FromIterator;
8use std::sync::Arc as StdArc;
9use std::sync::atomic::{AtomicBool, Ordering};
10use std::time::{Duration, SystemTime};
11
12use async_recursion::async_recursion;
13use content_security_policy::percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
14use devtools_traits::ChromeToDevtoolsControlMsg;
15use embedder_traits::{AuthenticationResponse, GenericEmbedderProxy};
16use futures::{TryFutureExt, TryStreamExt, future};
17use headers::authorization::Basic;
18use headers::{
19    AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowMethods,
20    AccessControlMaxAge, AccessControlRequestMethod, Authorization, CacheControl, ContentLength,
21    HeaderMapExt, IfModifiedSince, LastModified, Pragma, Referer, StrictTransportSecurity,
22    UserAgent,
23};
24use http::header::{
25    self, ACCEPT, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_REQUEST_HEADERS, AUTHORIZATION,
26    CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LOCATION, CONTENT_TYPE, HeaderValue, RANGE,
27    WWW_AUTHENTICATE,
28};
29use http::{HeaderMap, Method, Request as HyperRequest, StatusCode};
30use http_body_util::combinators::BoxBody;
31use http_body_util::{BodyExt, Full};
32use hyper::Response as HyperResponse;
33use hyper::body::{Bytes, Frame};
34use hyper::ext::ReasonPhrase;
35use hyper::header::{HeaderName, TRANSFER_ENCODING};
36use ipc_channel::IpcError;
37use ipc_channel::ipc::{self, IpcSender};
38use ipc_channel::router::ROUTER;
39use log::{debug, error, info, log_enabled, warn};
40use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
41use net_traits::blob_url_store::UrlWithBlobClaim;
42use net_traits::fetch::headers::get_value_from_header_list;
43use net_traits::http_status::HttpStatus;
44use net_traits::policy_container::{EmbedderPolicyValue, RequestPolicyContainer};
45use net_traits::pub_domains::{is_same_site, reg_suffix};
46use net_traits::request::{
47    BodyChunkRequest, BodyChunkResponse, CacheMode, CredentialsMode, Destination, Initiator,
48    Origin, RedirectMode, Referrer, Request, RequestBuilder, RequestClient, RequestMode,
49    ResponseTainting, ServiceWorkersMode, TraversableForUserPrompts, get_cors_unsafe_header_names,
50    is_cors_non_wildcard_request_header_name, is_cors_safelisted_method,
51    is_cors_safelisted_request_header,
52};
53use net_traits::response::{CacheState, RedirectTaint, Response, ResponseBody, ResponseType};
54use net_traits::{
55    CookieSource, DOCUMENT_ACCEPT_HEADER_VALUE, DiscardFetch, NetworkError, RedirectEndValue,
56    RedirectStartValue, ReferrerPolicy, ResourceAttribute, ResourceFetchTimingContainer,
57    ResourceTimeValue, TlsSecurityInfo, TlsSecurityState,
58};
59use parking_lot::{Mutex, RwLock};
60use profile_traits::mem::{Report, ReportKind};
61use profile_traits::path;
62#[cfg(feature = "tracing")]
63use profile_traits::trace_span;
64use rustc_hash::FxHashMap;
65use servo_base::cross_process_instant::CrossProcessInstant;
66use servo_base::generic_channel::GenericSharedMemory;
67use servo_base::id::{BrowsingContextId, HistoryStateId, PipelineId};
68use servo_config::pref;
69use servo_url::{ImmutableOrigin, ServoUrl};
70use tokio::sync::mpsc::{
71    Receiver as TokioReceiver, Sender as TokioSender, UnboundedReceiver, UnboundedSender, channel,
72    unbounded_channel,
73};
74use tokio_stream::wrappers::ReceiverStream;
75#[cfg(feature = "tracing")]
76use tracing::Instrument;
77
78use crate::async_runtime::spawn_task;
79use crate::connector::{
80    CertificateErrorOverrideManager, ServoClient, TlsHandshakeInfo, create_tls_config,
81};
82use crate::cookie::ServoCookie;
83use crate::cookie_storage::CookieStorage;
84use crate::decoder::Decoder;
85use crate::devtools::{
86    prepare_devtools_request, send_request_to_devtools, send_response_values_to_devtools,
87};
88use crate::embedder::NetToEmbedderMsg;
89use crate::fetch::cors_cache::CorsCache;
90use crate::fetch::fetch_params::FetchParams;
91use crate::fetch::headers::{SecFetchDest, SecFetchMode, SecFetchSite, SecFetchUser};
92use crate::fetch::methods::{Data, DoneChannel, FetchContext, Target, fetch, main_fetch};
93use crate::hsts::HstsList;
94use crate::http_cache::{
95    CacheKey, CachedResourcesOrGuard, HttpCache, ValidationStatus, construct_response,
96    invalidate_cached_resources, refresh,
97};
98use crate::resource_thread::{AuthCache, AuthCacheEntry};
99use crate::websocket_loader::start_websocket;
100
101/// The various states an entry of the HttpCache can be in.
102#[derive(Clone, Debug, Eq, PartialEq)]
103pub enum HttpCacheEntryState {
104    /// The entry is fully up-to-date,
105    /// there are no pending concurrent stores,
106    /// and it is ready to construct cached responses.
107    ReadyToConstruct,
108    /// The entry is pending a number of concurrent stores.
109    PendingStore(usize),
110}
111
112pub struct HttpState {
113    pub hsts_list: RwLock<HstsList>,
114    pub cookie_jar: RwLock<CookieStorage>,
115    pub http_cache: HttpCache,
116    pub auth_cache: RwLock<AuthCache>,
117    pub history_states: RwLock<FxHashMap<HistoryStateId, Vec<u8>>>,
118    pub client: ServoClient,
119    pub override_manager: CertificateErrorOverrideManager,
120    pub embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
121}
122
123impl HttpState {
124    pub(crate) fn memory_reports(&self, suffix: &str, ops: &mut MallocSizeOfOps) -> Vec<Report> {
125        vec![
126            Report {
127                path: path!["memory-cache", suffix],
128                kind: ReportKind::ExplicitJemallocHeapSize,
129                size: self.http_cache.size_of(ops),
130            },
131            Report {
132                path: path!["hsts-list", suffix],
133                kind: ReportKind::ExplicitJemallocHeapSize,
134                size: self.hsts_list.read().size_of(ops),
135            },
136            Report {
137                path: path!["auth cache", suffix],
138                kind: ReportKind::ExplicitJemallocHeapSize,
139                size: self.auth_cache.read().size_of(ops),
140            },
141            Report {
142                path: path!["cookie storage", suffix],
143                kind: ReportKind::ExplicitJemallocHeapSize,
144                size: self.cookie_jar.read().size_of(ops),
145            },
146        ]
147    }
148
149    async fn request_authentication(
150        &self,
151        request: &Request,
152        response: &Response,
153    ) -> Option<AuthenticationResponse> {
154        // We do not make an authentication request for non-WebView associated HTTP requests.
155        let webview_id = request.target_webview_id?;
156        let for_proxy = response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED;
157
158        // If this is not actually a navigation request return None.
159        if request.mode != RequestMode::Navigate {
160            return None;
161        }
162
163        let (sender, receiver) = tokio::sync::oneshot::channel();
164        self.embedder_proxy
165            .send(NetToEmbedderMsg::RequestAuthentication(
166                webview_id,
167                request.url(),
168                for_proxy,
169                sender,
170            ));
171        receiver.await.ok()?
172    }
173}
174
175/// Step 11 of <https://fetch.spec.whatwg.org/#concept-fetch>.
176pub(crate) fn set_default_accept(request: &mut Request) {
177    // Step 11. If request’s header list does not contain `Accept`, then:
178    if request.headers.contains_key(header::ACCEPT) {
179        return;
180    }
181
182    // Step 11.2. If request’s initiator is "prefetch", then set value to the document `Accept` header value.
183    let value = if request.initiator == Initiator::Prefetch {
184        DOCUMENT_ACCEPT_HEADER_VALUE
185    } else {
186        // Step 11.3. Otherwise, the user agent should set value to the first matching statement,
187        // if any, switching on request’s destination:
188        match request.destination {
189            Destination::Document | Destination::Frame | Destination::IFrame => {
190                DOCUMENT_ACCEPT_HEADER_VALUE
191            },
192            Destination::Image => {
193                HeaderValue::from_static("image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5")
194            },
195            Destination::Json => HeaderValue::from_static("application/json,*/*;q=0.5"),
196            Destination::Style => HeaderValue::from_static("text/css,*/*;q=0.1"),
197            // Step 11.1. Let value be `*/*`.
198            _ => HeaderValue::from_static("*/*"),
199        }
200    };
201
202    // Step 11.4. Append (`Accept`, value) to request’s header list.
203    request.headers.insert(header::ACCEPT, value);
204}
205
206fn set_default_accept_encoding(headers: &mut HeaderMap) {
207    if headers.contains_key(header::ACCEPT_ENCODING) {
208        return;
209    }
210
211    // TODO(eijebong): Change this once typed headers are done
212    headers.insert(
213        header::ACCEPT_ENCODING,
214        HeaderValue::from_static("gzip, deflate, br, zstd"),
215    );
216}
217
218/// <https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-state-no-referrer-when-downgrade>
219fn no_referrer_when_downgrade(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
220    // Step 1
221    if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
222        return None;
223    }
224    // Step 2
225    strip_url_for_use_as_referrer(referrer_url, false)
226}
227
228/// <https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin>
229fn strict_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
230    // Step 1
231    if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
232        return None;
233    }
234    // Step 2
235    strip_url_for_use_as_referrer(referrer_url, true)
236}
237
238/// <https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-strict-origin-when-cross-origin>
239fn strict_origin_when_cross_origin(
240    referrer_url: ServoUrl,
241    current_url: ServoUrl,
242) -> Option<ServoUrl> {
243    // Step 1
244    if referrer_url.origin() == current_url.origin() {
245        return strip_url_for_use_as_referrer(referrer_url, false);
246    }
247    // Step 2
248    if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
249        return None;
250    }
251    // Step 3
252    strip_url_for_use_as_referrer(referrer_url, true)
253}
254
255/// <https://html.spec.whatwg.org/multipage/#schemelessly-same-site>
256fn is_schemelessy_same_site(site_a: &ImmutableOrigin, site_b: &ImmutableOrigin) -> bool {
257    // Step 1
258    if !site_a.is_tuple() && !site_b.is_tuple() && site_a == site_b {
259        true
260    } else if site_a.is_tuple() && site_b.is_tuple() {
261        // Step 2.1
262        let host_a = site_a.host().map(|h| h.to_string()).unwrap_or_default();
263        let host_b = site_b.host().map(|h| h.to_string()).unwrap_or_default();
264
265        let host_a_reg = reg_suffix(&host_a);
266        let host_b_reg = reg_suffix(&host_b);
267
268        // Step 2.2-2.3
269        (site_a.host() == site_b.host() && host_a_reg.is_empty()) ||
270            (host_a_reg == host_b_reg && !host_a_reg.is_empty())
271    } else {
272        // Step 3
273        false
274    }
275}
276
277/// <https://w3c.github.io/webappsec-referrer-policy/#strip-url>
278fn strip_url_for_use_as_referrer(mut url: ServoUrl, origin_only: bool) -> Option<ServoUrl> {
279    const MAX_REFERRER_URL_LENGTH: usize = 4096;
280    // Step 2
281    if url.is_local_scheme() {
282        return None;
283    }
284    // Step 3-6
285    {
286        let url = url.as_mut_url();
287        let _ = url.set_username("");
288        let _ = url.set_password(None);
289        url.set_fragment(None);
290        // Note: The result of serializing referrer url should not be
291        // greater than 4096 as specified in Step 6 of
292        // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
293        if origin_only || url.as_str().len() > MAX_REFERRER_URL_LENGTH {
294            url.set_path("");
295            url.set_query(None);
296        }
297    }
298    // Step 7
299    Some(url)
300}
301
302/// <https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-same-origin>
303fn same_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
304    // Step 1
305    if referrer_url.origin() == current_url.origin() {
306        return strip_url_for_use_as_referrer(referrer_url, false);
307    }
308    // Step 2
309    None
310}
311
312/// <https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-origin-when-cross-origin>
313fn origin_when_cross_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
314    // Step 1
315    if referrer_url.origin() == current_url.origin() {
316        return strip_url_for_use_as_referrer(referrer_url, false);
317    }
318    // Step 2
319    strip_url_for_use_as_referrer(referrer_url, true)
320}
321
322/// <https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer>
323pub fn determine_requests_referrer(
324    referrer_policy: ReferrerPolicy,
325    referrer_source: ServoUrl,
326    current_url: ServoUrl,
327) -> Option<ServoUrl> {
328    match referrer_policy {
329        ReferrerPolicy::EmptyString | ReferrerPolicy::NoReferrer => None,
330        ReferrerPolicy::Origin => strip_url_for_use_as_referrer(referrer_source, true),
331        ReferrerPolicy::UnsafeUrl => strip_url_for_use_as_referrer(referrer_source, false),
332        ReferrerPolicy::StrictOrigin => strict_origin(referrer_source, current_url),
333        ReferrerPolicy::StrictOriginWhenCrossOrigin => {
334            strict_origin_when_cross_origin(referrer_source, current_url)
335        },
336        ReferrerPolicy::SameOrigin => same_origin(referrer_source, current_url),
337        ReferrerPolicy::OriginWhenCrossOrigin => {
338            origin_when_cross_origin(referrer_source, current_url)
339        },
340        ReferrerPolicy::NoReferrerWhenDowngrade => {
341            no_referrer_when_downgrade(referrer_source, current_url)
342        },
343    }
344}
345
346fn set_request_cookies(
347    url: &ServoUrl,
348    headers: &mut HeaderMap,
349    cookie_jar: &RwLock<CookieStorage>,
350) {
351    let mut cookie_jar = cookie_jar.write();
352    cookie_jar.remove_expired_cookies_for_url(url);
353    if let Some(cookie_list) = cookie_jar.cookies_for_url(url, CookieSource::HTTP) &&
354        let Ok(cookie_list_header_value) = HeaderValue::from_bytes(cookie_list.as_bytes())
355    {
356        headers.insert(header::COOKIE, cookie_list_header_value);
357    }
358}
359
360fn set_cookie_for_url(cookie_jar: &RwLock<CookieStorage>, request: &ServoUrl, cookie_val: &str) {
361    let mut cookie_jar = cookie_jar.write();
362    let source = CookieSource::HTTP;
363
364    if let Some(cookie) = ServoCookie::from_cookie_string(cookie_val, request, source) {
365        cookie_jar.push(cookie, request, source);
366    }
367}
368
369fn set_cookies_from_headers(
370    url: &ServoUrl,
371    headers: &HeaderMap,
372    cookie_jar: &RwLock<CookieStorage>,
373) {
374    for cookie in headers.get_all(header::SET_COOKIE) {
375        let cookie_bytes = cookie.as_bytes();
376        if !ServoCookie::is_valid_name_or_value(cookie_bytes) {
377            continue;
378        }
379        if let Ok(cookie_str) = std::str::from_utf8(cookie_bytes) {
380            set_cookie_for_url(cookie_jar, url, cookie_str);
381        }
382    }
383}
384
385fn build_tls_security_info(handshake: &TlsHandshakeInfo, hsts_enabled: bool) -> TlsSecurityInfo {
386    // Simplified security state determination:
387    // Servo uses rustls, which only supports TLS 1.2+ and secure cipher suites (GCM, ChaCha20-Poly1305).
388    // rustls does NOT support TLS 1.0, TLS 1.1, SSL, or weak ciphers (RC4, 3DES, CBC, etc).
389    // Therefore, any successful TLS connection is secure by design.
390    //
391    // We only check for missing handshake information as a defensive measure.
392
393    let state = if handshake.protocol_version.is_none() || handshake.cipher_suite.is_none() {
394        // Missing handshake information indicates an incomplete or failed connection
395        TlsSecurityState::Insecure
396    } else {
397        // rustls guarantees TLS 1.2+ with secure ciphers
398        TlsSecurityState::Secure
399    };
400
401    TlsSecurityInfo {
402        state,
403        weakness_reasons: Vec::new(), // rustls never negotiates weak crypto
404        protocol_version: handshake.protocol_version.clone(),
405        cipher_suite: handshake.cipher_suite.clone(),
406        kea_group_name: handshake.kea_group_name.clone(),
407        signature_scheme_name: handshake.signature_scheme_name.clone(),
408        alpn_protocol: handshake.alpn_protocol.clone(),
409        certificate_chain_der: handshake.certificate_chain_der.clone(),
410        certificate_transparency: None,
411        hsts: hsts_enabled,
412        hpkp: false,
413        used_ech: handshake.used_ech,
414        used_delegated_credentials: false,
415        used_ocsp: false,
416        used_private_dns: false,
417    }
418}
419
420fn auth_from_cache(
421    auth_cache: &RwLock<AuthCache>,
422    origin: &ImmutableOrigin,
423) -> Option<Authorization<Basic>> {
424    if let Some(auth_entry) = auth_cache
425        .read()
426        .entries
427        .get(origin.ascii_serialization().as_ref())
428    {
429        let user_name = &auth_entry.user_name;
430        let password = &auth_entry.password;
431        Some(Authorization::basic(user_name, password))
432    } else {
433        None
434    }
435}
436
437/// Messages from the IPC route to the fetch worker,
438/// used to fill the body with bytes coming-in over IPC.
439enum BodyChunk {
440    /// A chunk of bytes.
441    Chunk(GenericSharedMemory),
442    /// Body is done.
443    Done,
444}
445
446/// The stream side of the body passed to hyper.
447enum BodyStream {
448    /// A receiver that can be used in Body::wrap_stream,
449    /// for streaming the request over the network.
450    Chunked(TokioReceiver<Result<Frame<Bytes>, hyper::Error>>),
451    /// A body whose bytes are buffered
452    /// and sent in one chunk over the network.
453    Buffered(UnboundedReceiver<BodyChunk>),
454}
455
456/// The sink side of the body passed to hyper,
457/// used to enqueue chunks.
458enum BodySink {
459    /// A Tokio sender used to feed chunks to the network stream.
460    Chunked(TokioSender<Result<Frame<Bytes>, hyper::Error>>),
461    /// A Crossbeam sender used to send chunks to the fetch worker,
462    /// where they will be buffered
463    /// in order to ensure they are not streamed them over the network.
464    Buffered(UnboundedSender<BodyChunk>),
465}
466
467impl BodySink {
468    fn transmit_bytes(&self, bytes: GenericSharedMemory) {
469        match self {
470            BodySink::Chunked(sender) => {
471                let sender = sender.clone();
472                spawn_task(async move {
473                    let _ = sender
474                        .send(Ok(Frame::data(Bytes::copy_from_slice(&bytes))))
475                        .await;
476                });
477            },
478            BodySink::Buffered(sender) => {
479                let _ = sender.send(BodyChunk::Chunk(bytes));
480            },
481        }
482    }
483
484    fn close(self) {
485        match self {
486            BodySink::Chunked(_) => {},
487            BodySink::Buffered(sender) => {
488                let _ = sender.send(BodyChunk::Done);
489            },
490        }
491    }
492}
493
494fn request_body_stream_closed_error(action: &str) -> NetworkError {
495    NetworkError::Crash(format!(
496        "Request body stream has already been closed while trying to {action}."
497    ))
498}
499
500fn log_request_body_stream_closed(action: &str, error: Option<&IpcError>) {
501    match error {
502        Some(error) => {
503            error!("Request body stream has already been closed while trying to {action}: {error}")
504        },
505        None => error!("Request body stream has already been closed while trying to {action}."),
506    }
507}
508
509fn log_fetch_terminated_send_failure(terminated_with_error: bool, context: &str) {
510    warn!(
511        "Failed to notify request-body stream termination state ({terminated_with_error}) while {context} because the receiver was already dropped."
512    );
513}
514
515const FRAGMENT: &AsciiSet = &CONTROLS.add(b'|').add(b'{').add(b'}');
516
517#[allow(clippy::too_many_arguments)]
518#[servo_tracing::instrument(skip_all, fields(url=url.as_str()))]
519/// This sets up the callback infrastructure to send body frames to `body_sender` and fires the client request.
520async fn obtain_response(
521    client: &ServoClient,
522    url: &ServoUrl,
523    method: &Method,
524    request_headers: &mut HeaderMap,
525    body_sender: Option<StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>>,
526    source_is_null: bool,
527    pipeline_id: &Option<PipelineId>,
528    request_id: Option<&str>,
529    destination: Destination,
530    is_xhr: bool,
531    context: &FetchContext,
532    fetch_terminated: UnboundedSender<bool>,
533    browsing_context_id: Option<BrowsingContextId>,
534) -> Result<(HyperResponse<Decoder>, Option<ChromeToDevtoolsControlMsg>), NetworkError> {
535    let mut headers = request_headers.clone();
536
537    let devtools_bytes = StdArc::new(Mutex::new(vec![]));
538
539    // https://url.spec.whatwg.org/#percent-encoded-bytes
540    let encoded_url = utf8_percent_encode(url.as_str(), FRAGMENT).to_string();
541
542    let request = if let Some(chunk_requester) = body_sender {
543        let (sink, stream) = if source_is_null {
544            // Step 4.2 of https://fetch.spec.whatwg.org/#concept-http-network-fetch
545            // TODO: this should not be set for HTTP/2(currently not supported?).
546            headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
547
548            let (sender, receiver) = channel(1);
549            (BodySink::Chunked(sender), BodyStream::Chunked(receiver))
550        } else {
551            // Note: Hyper seems to already buffer bytes when the request appears not stream-able,
552            // see https://github.com/hyperium/hyper/issues/2232#issuecomment-644322104
553            //
554            // However since this doesn't appear documented, and we're using an ancient version,
555            // for now we buffer manually to ensure we don't stream requests
556            // to servers that might not know how to handle them.
557            let (sender, receiver) = unbounded_channel();
558            (BodySink::Buffered(sender), BodyStream::Buffered(receiver))
559        };
560
561        obtain_response_setup_router_callback(
562            devtools_bytes.clone(),
563            chunk_requester,
564            sink,
565            fetch_terminated,
566        )?;
567
568        let body = match stream {
569            BodyStream::Chunked(receiver) => {
570                let stream = ReceiverStream::new(receiver);
571                BoxBody::new(http_body_util::StreamBody::new(stream))
572            },
573            BodyStream::Buffered(mut receiver) => {
574                // Accumulate bytes received over IPC into a vector.
575                let mut body = vec![];
576                loop {
577                    match receiver.recv().await {
578                        Some(BodyChunk::Chunk(bytes)) => {
579                            body.extend_from_slice(&bytes);
580                        },
581                        Some(BodyChunk::Done) => break,
582                        None => warn!("Failed to read all chunks from request body."),
583                    }
584                }
585                Full::new(body.into()).map_err(|_| unreachable!()).boxed()
586            },
587        };
588        HyperRequest::builder()
589            .method(method)
590            .uri(encoded_url)
591            .body(body)
592    } else {
593        HyperRequest::builder()
594            .method(method)
595            .uri(encoded_url)
596            .body(
597                http_body_util::Empty::new()
598                    .map_err(|_| unreachable!())
599                    .boxed(),
600            )
601    };
602
603    // TODO(#21261) connect_start: set if a persistent connection is *not* used and the last non-redirected
604    // fetch passes the timing allow check
605    let connect_start = CrossProcessInstant::now();
606    context.timing.set_attributes(&[
607        ResourceAttribute::DomainLookupStart,
608        ResourceAttribute::ConnectStart(connect_start),
609    ]);
610
611    // TODO: We currently don't know when the handhhake before the connection is done
612    // so our best bet would be to set `secure_connection_start` here when we are currently
613    // fetching on a HTTPS url.
614    if url.scheme() == "https" {
615        context
616            .timing
617            .set_attribute(ResourceAttribute::SecureConnectionStart);
618    }
619
620    let mut request = match request {
621        Ok(request) => request,
622        Err(error) => return Err(NetworkError::HttpError(error.to_string())),
623    };
624    *request.headers_mut() = headers.clone();
625
626    let connect_end = CrossProcessInstant::now();
627    context
628        .timing
629        .set_attribute(ResourceAttribute::ConnectEnd(connect_end));
630
631    let request_id = request_id.map(|v| v.to_owned());
632    let pipeline_id = *pipeline_id;
633    let closure_url = url.clone();
634    let method = method.clone();
635    let send_start = CrossProcessInstant::now();
636
637    let host = request.uri().host().unwrap_or("").to_owned();
638    let override_manager = context.state.override_manager.clone();
639    let headers = headers.clone();
640    let is_secure_scheme = url.is_secure_scheme();
641
642    // Generally, we use a persistent connection, so we will also set other PerformanceResourceTiming
643    //   attributes to this as well (domain_lookup_start, domain_lookup_end, connect_start, connect_end,
644    //   secure_connection_start)
645    context
646        .timing
647        .set_attribute(ResourceAttribute::RequestStart);
648
649    let client_future = client
650        .request(request)
651        .and_then(move |res| {
652            let send_end = CrossProcessInstant::now();
653
654            // TODO(#21271) response_start: immediately after receiving first byte of response
655
656            let msg = if let Some(request_id) = request_id {
657                if let Some(pipeline_id) = pipeline_id {
658                    if let Some(browsing_context_id) = browsing_context_id {
659                        Some(prepare_devtools_request(
660                            request_id,
661                            closure_url,
662                            method.clone(),
663                            headers,
664                            Some(devtools_bytes.lock().clone()),
665                            pipeline_id,
666                            (connect_end - connect_start).unsigned_abs(),
667                            (send_end - send_start).unsigned_abs(),
668                            destination,
669                            is_xhr,
670                            browsing_context_id,
671                        ))
672                    } else {
673                        debug!("Not notifying devtools (no browsing_context_id)");
674                        None
675                    }
676                    // TODO: ^This is not right, connect_start is taken before contructing the
677                    // request and connect_end at the end of it. send_start is takend before the
678                    // connection too. I'm not sure it's currently possible to get the time at the
679                    // point between the connection and the start of a request.
680                } else {
681                    debug!("Not notifying devtools (no pipeline_id)");
682                    None
683                }
684            } else {
685                debug!("Not notifying devtools (no request_id)");
686                None
687            };
688
689            future::ready(Ok((
690                Decoder::detect(res.map(|r| r.boxed()), is_secure_scheme),
691                msg,
692            )))
693        })
694        .map_err(move |error| {
695            warn!("network error: {error:?}");
696            NetworkError::from_hyper_error(
697                &error,
698                override_manager.remove_certificate_failing_verification(host.as_str()),
699            )
700        });
701
702    #[cfg(feature = "tracing")]
703    {
704        client_future.instrument(trace_span!("HyperRequest")).await
705    }
706
707    #[cfg(not(feature = "tracing"))]
708    {
709        client_future.await
710    }
711}
712
713/// Setup the callback mechanism to forward chunks from the request received to the `chunk_requester`.
714fn obtain_response_setup_router_callback(
715    devtools_bytes: StdArc<Mutex<Vec<u8>>>,
716    chunk_requester: StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>,
717    sink: BodySink,
718    fetch_terminated: UnboundedSender<bool>,
719) -> Result<(), NetworkError> {
720    let (body_chan, body_port) = ipc::channel().unwrap();
721
722    {
723        let mut lock = chunk_requester.lock();
724        if let Some(chunk_requester) = lock.as_mut() {
725            if let Err(error) = chunk_requester.send(BodyChunkRequest::Connect(body_chan)) {
726                log_request_body_stream_closed("connect to the request body stream", Some(&error));
727                return Err(request_body_stream_closed_error(
728                    "connect to the request body stream",
729                ));
730            }
731
732            // https://fetch.spec.whatwg.org/#concept-request-transmit-body
733            // Request the first chunk, corresponding to Step 3 and 4.
734            if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
735                log_request_body_stream_closed(
736                    "request the first request body chunk",
737                    Some(&error),
738                );
739                return Err(request_body_stream_closed_error(
740                    "request the first request body chunk",
741                ));
742            }
743        } else {
744            log_request_body_stream_closed("connect to the request body stream", None);
745            return Err(request_body_stream_closed_error(
746                "connect to the request body stream",
747            ));
748        }
749    }
750
751    let mut sink = Some(sink);
752
753    ROUTER.add_typed_route(
754        body_port,
755        Box::new(move |message| {
756            info!("Received message");
757            let bytes = match message.unwrap() {
758                BodyChunkResponse::Chunk(bytes) => bytes,
759                BodyChunkResponse::Done => {
760                    // Step 2.2.2. If fetchParams’s process request end-of-body is non-null,
761                    // then run fetchParams’s process request end-of-body.
762                    if fetch_terminated.send(false).is_err() {
763                        log_fetch_terminated_send_failure(
764                            false,
765                            "handling request body completion",
766                        );
767                    }
768                    if let Some(sink) = sink.take() {
769                        sink.close();
770                    }
771
772                    return;
773                },
774                BodyChunkResponse::Error => {
775                    // Step 4 and/or 5.
776                    // TODO: differentiate between the two steps,
777                    // where step 5 requires setting an `aborted` flag on the fetch.
778                    if fetch_terminated.send(true).is_err() {
779                        log_fetch_terminated_send_failure(
780                            true,
781                            "handling request body stream error",
782                        );
783                    }
784                    if let Some(sink) = sink.take() {
785                        sink.close();
786                    }
787
788                    return;
789                },
790            };
791
792            devtools_bytes.lock().extend_from_slice(&bytes);
793
794            // Step 5.1.2.2, transmit chunk over the network,
795            // currently implemented by sending the bytes to the fetch worker.
796            {
797                let Some(sink) = sink.as_ref() else {
798                    return;
799                };
800                sink.transmit_bytes(bytes);
801            }
802
803            // Step 5.1.2.3
804            // Request the next chunk.
805            let mut chunk_requester = chunk_requester.lock();
806            if let Some(chunk_requester) = chunk_requester.as_mut() {
807                if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
808                    log_request_body_stream_closed(
809                        "request the next request body chunk",
810                        Some(&error),
811                    );
812                    if fetch_terminated.send(true).is_err() {
813                        log_fetch_terminated_send_failure(
814                            true,
815                            "handling failure to request the next request body chunk",
816                        );
817                    }
818                    if let Some(sink) = sink.take() {
819                        sink.close();
820                    }
821                }
822            } else {
823                log_request_body_stream_closed("request the next request body chunk", None);
824                if fetch_terminated.send(true).is_err() {
825                    log_fetch_terminated_send_failure(
826                        true,
827                        "handling a closed request body stream while requesting the next chunk",
828                    );
829                }
830                if let Some(sink) = sink.take() {
831                    sink.close();
832                }
833            }
834        }),
835    );
836
837    Ok(())
838}
839
840/// [HTTP fetch](https://fetch.spec.whatwg.org/#concept-http-fetch)
841#[async_recursion]
842#[allow(clippy::too_many_arguments)]
843pub(crate) async fn http_fetch(
844    fetch_params: &mut FetchParams,
845    cache: &mut CorsCache,
846    cors_flag: bool,
847    cors_preflight_flag: bool,
848    authentication_fetch_flag: bool,
849    target: Target<'async_recursion>,
850    done_chan: &mut DoneChannel,
851    context: &FetchContext,
852) -> Response {
853    // This is a new async fetch, reset the channel we are waiting on
854    *done_chan = None;
855    // Step 1. Let request be fetchParams’s request.
856    let request = &mut fetch_params.request;
857
858    // Step 2. Let response and internalResponse be null.
859    let mut response: Option<Response> = None;
860
861    // Step 3. If request’s service-workers mode is "all", then
862    if request.service_workers_mode == ServiceWorkersMode::All {
863        // TODO: Substep 1
864        // Set response to the result of invoking handle fetch for request.
865
866        // Substep 2
867        if let Some(ref res) = response {
868            // Subsubstep 1
869            // TODO: transmit body for request
870
871            // Subsubstep 2
872            // nothing to do, since actual_response is a function on response
873
874            // Subsubstep 3
875            if (res.response_type == ResponseType::Opaque && request.mode != RequestMode::NoCors) ||
876                (res.response_type == ResponseType::OpaqueRedirect &&
877                    request.redirect_mode != RedirectMode::Manual) ||
878                (res.url_list.len() > 1 && request.redirect_mode != RedirectMode::Follow) ||
879                res.is_network_error()
880            {
881                return Response::network_error(NetworkError::ConnectionFailure);
882            }
883
884            // Subsubstep 4
885            // TODO: set response's CSP list on actual_response
886        }
887    }
888
889    // Step 4. If response is null, then:
890    if response.is_none() {
891        // Step 4.1. If makeCORSPreflight is true and one of these conditions is true:
892        if cors_preflight_flag {
893            let method_cache_match = cache.match_method(request, request.method.clone());
894
895            // There is no method cache entry match for request’s method using request, and either
896            // request’s method is not a CORS-safelisted method or request’s use-CORS-preflight flag
897            // is set.
898            let method_mismatch = !method_cache_match &&
899                (!is_cors_safelisted_method(&request.method) || request.use_cors_preflight);
900
901            // There is at least one item in the CORS-unsafe request-header names with request’s
902            // header list for which there is no header-name cache entry match using request.
903            let header_mismatch = request.headers.iter().any(|(name, value)| {
904                !cache.match_header(request, name) &&
905                    !is_cors_safelisted_request_header(&name, &value)
906            });
907
908            // Then:
909            if method_mismatch || header_mismatch {
910                // Step 4.1.1. Let preflightResponse be the result of running
911                // CORS-preflight fetch given request.
912                let preflight_response = cors_preflight_fetch(request, cache, context).await;
913                // Step 4.1.2. If preflightResponse is a network error, then return preflightResponse.
914                if let Some(error) = preflight_response.get_network_error() {
915                    return Response::network_error(error.clone());
916                }
917            }
918        }
919
920        // Step 4.2. If request’s redirect mode is "follow",
921        // then set request’s service-workers mode to "none".
922        if request.redirect_mode == RedirectMode::Follow {
923            request.service_workers_mode = ServiceWorkersMode::None;
924        }
925
926        // Step 4.3. Set response and internalResponse to the result of
927        // running HTTP-network-or-cache fetch given fetchParams.
928        let mut fetch_result = http_network_or_cache_fetch(
929            fetch_params,
930            authentication_fetch_flag,
931            cors_flag,
932            done_chan,
933            context,
934        )
935        .await;
936
937        // Step 4.4. If request’s response tainting is "cors" and a CORS check for request
938        // and response returns failure, then return a network error.
939        if cors_flag && cors_check(&fetch_params.request, &fetch_result).is_err() {
940            return Response::network_error(NetworkError::CorsGeneral);
941        }
942
943        // Step 4.5. If the TAO check for request and response returns failure,
944        // then set request’s timing allow failed flag.
945        if let Err(()) = tao_check(&fetch_params.request, &fetch_result) {
946            context.timing.inner().mark_timing_check_failed();
947        }
948        fetch_result.return_internal = false;
949        response = Some(fetch_result);
950    }
951
952    let request = &mut fetch_params.request;
953
954    // response is guaranteed to be something by now
955    let mut response = response.unwrap();
956
957    // Step 5: If either request’s response tainting or response’s type is "opaque",
958    // and the cross-origin resource policy check with request’s origin, request’s client,
959    // request’s destination, and internalResponse returns blocked, then return a network error.
960    if (request.response_tainting == ResponseTainting::Opaque ||
961        response.response_type == ResponseType::Opaque) &&
962        request.client.as_ref().is_some_and(|client| {
963            cross_origin_resource_policy_check(
964                &request.origin,
965                client,
966                &response,
967                ForNavigation::No,
968            ) == CrossOriginResourcePolicy::Blocked
969        })
970    {
971        return Response::network_error(NetworkError::CrossOriginResponse);
972    }
973
974    // Step 6. If internalResponse’s status is a redirect status:
975    if response
976        .actual_response()
977        .status
978        .try_code()
979        .is_some_and(is_redirect_status)
980    {
981        // TODO Step 6.1. If request is a navigation request, then append to a request’s navigation
982        // timing allow values list given request and internalResponse.
983
984        // Step 6.2. If internalResponse’s status is not 303, request’s body is non-null,
985        // and the connection uses HTTP/2, then user agents may, and are even encouraged to,
986        // transmit an RST_STREAM frame.
987        if response.actual_response().status != StatusCode::SEE_OTHER {
988            // TODO: send RST_STREAM frame
989        }
990
991        // Step 6.3. Switch on request’s redirect mode:
992        response = match request.redirect_mode {
993            // Step 6.3."error".1. Set response to a network error.
994            RedirectMode::Error => Response::network_error(NetworkError::RedirectError),
995            RedirectMode::Manual => {
996                // Step 6.3."manual".1. If request’s mode is "navigate", then set fetchParams’s controller’s
997                // next manual redirect steps to run HTTP-redirect fetch given fetchParams and response.
998                if request.mode == RequestMode::Navigate {
999                    // TODO: We don't implement Fetch controller. Instead, we update the location url
1000                    // of the response here and don't call `http_redirect_fetch`. That's get called later.
1001                    // Once we have a fetch controller here, we should update the code as specced.
1002                    let location_url =
1003                        location_url_for_response(&response, request.current_url().fragment());
1004                    response.actual_response_mut().location_url = location_url;
1005                    response
1006                } else {
1007                    // Step 6.3."manual".2. Otherwise, set response to an opaque-redirect filtered
1008                    // response whose internal response is internalResponse.
1009                    response.to_filtered(ResponseType::OpaqueRedirect)
1010                }
1011            },
1012            RedirectMode::Follow => {
1013                // TODO Step 6.3."follow".1. Run the WebDriver BiDi response completed steps with
1014                // request and response.
1015                // set back to default
1016                response.return_internal = true;
1017
1018                // Step 6.3."follow".2. Set response to the result of running HTTP-redirect fetch
1019                // given fetchParams and response.
1020                http_redirect_fetch(
1021                    fetch_params,
1022                    cache,
1023                    response,
1024                    cors_flag,
1025                    target,
1026                    done_chan,
1027                    context,
1028                )
1029                .await
1030            },
1031        };
1032    }
1033
1034    // set back to default
1035    response.return_internal = true;
1036    context
1037        .timing
1038        .set_attribute(ResourceAttribute::RedirectCount(
1039            fetch_params.request.redirect_count as u16,
1040        ));
1041
1042    response.resource_timing = context.timing.clone();
1043
1044    // Step 7. Return response
1045    response
1046}
1047
1048/// <https://fetch.spec.whatwg.org/#concept-tao-check>
1049fn tao_check(request: &Request, response: &Response) -> Result<(), ()> {
1050    // Step 1. Assert: request’s origin is not "client".
1051    let Origin::Origin(ref request_origin) = request.origin else {
1052        unreachable!("origin cannot be \"client\" at this point");
1053    };
1054
1055    // Step 2. If request’s timing allow failed flag is set, then return failure.
1056
1057    // Step 3. Let values be the result of getting, decoding, and splitting `Timing-Allow-Origin`
1058    // from response’s header list.
1059    let values: Vec<&str> = response
1060        .headers
1061        .get_all("Timing-Allow-Origin")
1062        .iter()
1063        .map(|header_value| header_value.to_str().unwrap_or(""))
1064        .collect();
1065
1066    // Step 4. If values contains "*", then return success.
1067    if values.contains(&"*") {
1068        return Ok(());
1069    }
1070
1071    // Step 5. If values contains the result of serializing a request origin with request, then
1072    // return success.
1073    if values
1074        .iter()
1075        .any(|header_str| *header_str == request_origin.ascii_serialization().as_ref())
1076    {
1077        return Ok(());
1078    }
1079
1080    // Step 6. If request’s mode is "navigate" and request’s current URL’s origin is not same origin
1081    // with request’s origin, then return failure.
1082    if request.mode == RequestMode::Navigate && request.current_url().origin() != *request_origin {
1083        return Err(());
1084    }
1085
1086    // Step 7. If request’s response tainting is "basic", then return success.
1087    if request.response_tainting == ResponseTainting::Basic {
1088        return Ok(());
1089    }
1090
1091    // Step 8. Return failure.
1092    Err(())
1093}
1094
1095// Convenience struct that implements Drop, for setting redirectEnd on function return
1096struct RedirectEndTimer(Option<ResourceFetchTimingContainer>);
1097
1098impl RedirectEndTimer {
1099    fn neuter(&mut self) {
1100        self.0 = None;
1101    }
1102}
1103
1104impl Drop for RedirectEndTimer {
1105    fn drop(&mut self) {
1106        let RedirectEndTimer(resource_fetch_timing_opt) = self;
1107
1108        resource_fetch_timing_opt.as_ref().map_or((), |t| {
1109            t.set_attribute(ResourceAttribute::RedirectEnd(RedirectEndValue::Zero));
1110        })
1111    }
1112}
1113
1114/// <https://fetch.spec.whatwg.org/#request-body-header-name>
1115static REQUEST_BODY_HEADER_NAMES: &[HeaderName] = &[
1116    CONTENT_ENCODING,
1117    CONTENT_LANGUAGE,
1118    CONTENT_LOCATION,
1119    CONTENT_TYPE,
1120];
1121
1122/// <https://fetch.spec.whatwg.org/#concept-response-location-url>
1123fn location_url_for_response(
1124    response: &Response,
1125    request_fragment: Option<&str>,
1126) -> Option<Result<ServoUrl, String>> {
1127    // Step 1. If response’s status is not a redirect status, then return null.
1128    assert!(
1129        response
1130            .actual_response()
1131            .status
1132            .try_code()
1133            .is_some_and(is_redirect_status)
1134    );
1135    // Step 2. Let location be the result of extracting header list values given `Location` and response’s header list.
1136    let mut location = response
1137        .actual_response()
1138        .headers
1139        .get(header::LOCATION)
1140        .and_then(|header_value| {
1141            HeaderValue::to_str(header_value)
1142                .map(|location_string| {
1143                    // Step 3. If location is a header value, then set location to the result of parsing location with response’s URL.
1144                    ServoUrl::parse_with_base(response.actual_response().url(), location_string)
1145                        .map_err(|error| error.to_string())
1146                })
1147                .ok()
1148        });
1149
1150    // Step 4. If location is a URL whose fragment is null, then set location’s fragment to requestFragment.
1151    if let Some(Ok(ref mut location)) = location &&
1152        location.fragment().is_none()
1153    {
1154        location.set_fragment(request_fragment);
1155    }
1156    // Step 5. Return location.
1157    location
1158}
1159
1160/// [HTTP redirect fetch](https://fetch.spec.whatwg.org/#http-redirect-fetch)
1161#[async_recursion]
1162pub async fn http_redirect_fetch(
1163    fetch_params: &mut FetchParams,
1164    cache: &mut CorsCache,
1165    mut response: Response,
1166    cors_flag: bool,
1167    target: Target<'async_recursion>,
1168    done_chan: &mut DoneChannel,
1169    context: &FetchContext,
1170) -> Response {
1171    let mut redirect_end_timer = RedirectEndTimer(Some(context.timing.clone()));
1172
1173    // Step 1. Let request be fetchParams’s request.
1174    let request = &mut fetch_params.request;
1175
1176    // Step 2. Let internalResponse be response, if response is not a filtered response; otherwise response’s internal response.
1177    assert!(response.return_internal);
1178
1179    // Step 3. Let locationURL be internalResponse’s location URL given request’s current URL’s fragment.
1180    let location_url = location_url_for_response(&response, request.current_url().fragment());
1181    response.actual_response_mut().location_url = location_url.clone();
1182
1183    let location_url = match location_url {
1184        // Step 4. If locationURL is null, then return response.
1185        None => return response,
1186        // Step 5. If locationURL is failure, then return a network error.
1187        Some(Err(err)) => {
1188            return Response::network_error(NetworkError::ResourceLoadError(
1189                "Location URL parse failure: ".to_owned() + &err,
1190            ));
1191        },
1192        // Step 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network error.
1193        Some(Ok(url)) if !matches!(url.scheme(), "http" | "https") => {
1194            return Response::network_error(NetworkError::UnsupportedScheme);
1195        },
1196        Some(Ok(url)) => url,
1197    };
1198
1199    // Step 1 of https://w3c.github.io/resource-timing/#dom-performanceresourcetiming-fetchstart
1200    // TODO: check origin and timing allow check
1201    // start_time should equal redirect_start if nonzero; else fetch_start
1202    // updates start_time only if redirect_start is nonzero (implying TAO)
1203    context.timing.set_attributes(&[
1204        ResourceAttribute::RedirectStart(RedirectStartValue::FetchStart),
1205        ResourceAttribute::FetchStart,
1206        ResourceAttribute::StartTime(ResourceTimeValue::FetchStart),
1207        ResourceAttribute::StartTime(ResourceTimeValue::RedirectStart),
1208    ]);
1209
1210    // Step 7: If request’s redirect count is 20, then return a network error.
1211    if request.redirect_count >= 20 {
1212        return Response::network_error(NetworkError::TooManyRedirects);
1213    }
1214
1215    // Step 8: Increase request’s redirect count by 1.
1216    request.redirect_count += 1;
1217
1218    // Step 9. If request’s mode is "cors", locationURL includes credentials,
1219    // and request’s origin is not same origin with locationURL’s origin, then return a network error.
1220    let same_origin = match request.origin {
1221        Origin::Origin(ref origin) => *origin == location_url.origin(),
1222        Origin::Client => panic!(
1223            "Request origin should not be client for {}",
1224            request.current_url()
1225        ),
1226    };
1227
1228    let has_credentials = has_credentials(&location_url);
1229
1230    if request.mode == RequestMode::CorsMode && !same_origin && has_credentials {
1231        return Response::network_error(NetworkError::CorsCredentials);
1232    }
1233
1234    if cors_flag && location_url.origin() != request.current_url().origin() {
1235        request.origin = Origin::Origin(ImmutableOrigin::new_opaque());
1236    }
1237
1238    // Step 10. If request’s response tainting is "cors" and locationURL includes credentials, then return a network error.
1239    if cors_flag && has_credentials {
1240        return Response::network_error(NetworkError::CorsCredentials);
1241    }
1242
1243    // Step 11: If internalResponse’s status is not 303, request’s body is non-null, and request’s
1244    // body’s source is null, then return a network error.
1245    if response.actual_response().status != StatusCode::SEE_OTHER &&
1246        request.body.as_ref().is_some_and(|b| b.source_is_null())
1247    {
1248        return Response::network_error(NetworkError::ConnectionFailure);
1249    }
1250
1251    // Step 12. If one of the following is true
1252    if response
1253        .actual_response()
1254        .status
1255        .try_code()
1256        .is_some_and(|code| {
1257            // internalResponse’s status is 301 or 302 and request’s method is `POST`
1258            ((code == StatusCode::MOVED_PERMANENTLY || code == StatusCode::FOUND) &&
1259                request.method == Method::POST) ||
1260                // internalResponse’s status is 303 and request’s method is not `GET` or `HEAD`
1261                (code == StatusCode::SEE_OTHER &&
1262                    request.method != Method::HEAD &&
1263                    request.method != Method::GET)
1264        })
1265    {
1266        // Step 12.1. Set request’s method to `GET` and request’s body to null.
1267        request.method = Method::GET;
1268        request.body = None;
1269        // Step 12.2. For each headerName of request-body-header name, delete headerName from request’s header list.
1270        for name in REQUEST_BODY_HEADER_NAMES {
1271            request.headers.remove(name);
1272        }
1273    }
1274
1275    // Step 13: If request’s current URL’s origin is not same origin with locationURL’s origin, then
1276    // for each headerName of CORS non-wildcard request-header name, delete headerName from
1277    // request’s header list.
1278    if location_url.origin() != request.current_url().origin() {
1279        // This list currently only contains the AUTHORIZATION header
1280        // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name
1281        request.headers.remove(AUTHORIZATION);
1282    }
1283
1284    // Step 14: If request’s body is non-null, then set request’s body to the body of the result of
1285    // safely extracting request’s body’s source.
1286    if let Some(body) = request.body.as_mut() {
1287        body.extract_source();
1288    }
1289
1290    // Steps 15-17 relate to timing, which is not implemented 1:1 with the spec.
1291
1292    // Step 18: Append locationURL to request’s URL list.
1293    request
1294        .url_list
1295        .push(UrlWithBlobClaim::from_url_without_having_claimed_blob(
1296            location_url,
1297        ));
1298
1299    // Step 19: Invoke set request’s referrer policy on redirect on request and internalResponse.
1300    set_requests_referrer_policy_on_redirect(request, response.actual_response());
1301
1302    // Step 20: Let recursive be true.
1303    // Step 21: If request’s redirect mode is "manual", then...
1304    let recursive_flag = request.redirect_mode != RedirectMode::Manual;
1305
1306    // Step 22: Return the result of running main fetch given fetchParams and recursive.
1307    let fetch_response = main_fetch(
1308        fetch_params,
1309        cache,
1310        recursive_flag,
1311        target,
1312        done_chan,
1313        context,
1314    )
1315    .await;
1316
1317    // TODO: timing allow check
1318    context.timing.set_attribute(ResourceAttribute::RedirectEnd(
1319        RedirectEndValue::ResponseEnd,
1320    ));
1321    redirect_end_timer.neuter();
1322
1323    fetch_response
1324}
1325
1326/// [HTTP network or cache fetch](https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch)
1327#[async_recursion]
1328#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
1329async fn http_network_or_cache_fetch(
1330    fetch_params: &mut FetchParams,
1331    authentication_fetch_flag: bool,
1332    cors_flag: bool,
1333    done_chan: &mut DoneChannel,
1334    context: &FetchContext,
1335) -> Response {
1336    // Step 2. Let httpFetchParams be null.
1337    let http_fetch_params: &mut FetchParams;
1338    let mut fetch_params_copy: FetchParams;
1339
1340    // Step 3. Let httpRequest be null. (See step 8 for initialization)
1341
1342    // Step 4. Let response be null.
1343    let mut response: Option<Response> = None;
1344
1345    // Step 7. Let the revalidatingFlag be unset.
1346    let mut revalidating_flag = false;
1347
1348    // TODO(#33616): Step 8. Run these steps, but abort when fetchParams is canceled:
1349    // Step 8.1. If request’s traversable for user prompts is "no-traversable"
1350    // and request’s redirect mode is "error", then set httpFetchParams to fetchParams and httpRequest to request.
1351    let http_request = if fetch_params.request.traversable_for_user_prompts ==
1352        TraversableForUserPrompts::NoTraversable &&
1353        fetch_params.request.redirect_mode == RedirectMode::Error
1354    {
1355        http_fetch_params = fetch_params;
1356        &mut http_fetch_params.request
1357    }
1358    // Step 8.2 Otherwise:
1359    else {
1360        // Step 8.2.1 - 8.2.3: Set httpRequest to a clone of request
1361        // and Set httpFetchParams to a copy of fetchParams.
1362        fetch_params_copy =
1363            std::mem::replace(fetch_params, FetchParams::new(fetch_params.request.clone()));
1364        http_fetch_params = &mut fetch_params_copy;
1365
1366        &mut http_fetch_params.request
1367    };
1368
1369    // Step 8.3: Let includeCredentials be true if one of:
1370    let include_credentials = match http_request.credentials_mode {
1371        // request’s credentials mode is "include"
1372        CredentialsMode::Include => true,
1373        // request’s credentials mode is "same-origin" and request’s response tainting is "basic"
1374        CredentialsMode::CredentialsSameOrigin
1375            if http_request.response_tainting == ResponseTainting::Basic =>
1376        {
1377            true
1378        },
1379        _ => false,
1380    };
1381
1382    // Step 8.4: If Cross-Origin-Embedder-Policy allows credentials with request returns false, then
1383    // set includeCredentials to false.
1384    // TODO(#33616): Requires request's client object
1385
1386    // Step 8.5 Let contentLength be httpRequest’s body’s length, if httpRequest’s body is non-null;
1387    // otherwise null.
1388    let content_length = http_request
1389        .body
1390        .as_ref()
1391        .and_then(|body| body.len().map(|size| size as u64));
1392
1393    // Step 8.6 Let contentLengthHeaderValue be null.
1394    let mut content_length_header_value = None;
1395
1396    // Step 8.7 If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`,
1397    // then set contentLengthHeaderValue to `0`.
1398    if http_request.body.is_none() && matches!(http_request.method, Method::POST | Method::PUT) {
1399        content_length_header_value = Some(0);
1400    }
1401
1402    // Step 8.8 If contentLength is non-null, then set contentLengthHeaderValue to contentLength,
1403    // serialized and isomorphic encoded.
1404    // NOTE: The header will later be serialized using HeaderMap::typed_insert
1405    if let Some(content_length) = content_length {
1406        content_length_header_value = Some(content_length);
1407    };
1408
1409    // Step 8.9 If contentLengthHeaderValue is non-null, then append (`Content-Length`, contentLengthHeaderValue)
1410    // to httpRequest’s header list.
1411    if let Some(content_length_header_value) = content_length_header_value {
1412        http_request
1413            .headers
1414            .typed_insert(ContentLength(content_length_header_value));
1415    }
1416
1417    // Step 8.10 If contentLength is non-null and httpRequest’s keepalive is true, then:
1418    if http_request.keep_alive &&
1419        let Some(content_length) = content_length
1420    {
1421        // Step 8.10.1. Let inflightKeepaliveBytes be 0.
1422        // Step 8.10.2. Let group be httpRequest’s client’s fetch group.
1423        // Step 8.10.3. Let inflightRecords be the set of fetch records
1424        // in group whose request’s keepalive is true and done flag is unset.
1425        let in_flight_keep_alive_bytes: u64 = context
1426            .in_flight_keep_alive_records
1427            .lock()
1428            .get(
1429                &http_request
1430                    .pipeline_id
1431                    .expect("Must always set a pipeline ID for keep-alive requests"),
1432            )
1433            .map(|records| {
1434                // Step 8.10.4. For each fetchRecord of inflightRecords:
1435                // Step 8.10.4.1. Let inflightRequest be fetchRecord’s request.
1436                // Step 8.10.4.2. Increment inflightKeepaliveBytes by inflightRequest’s body’s length.
1437                records
1438                    .iter()
1439                    .map(|record| {
1440                        if record.request_id == http_request.id {
1441                            // Don't double count for this request. We have already added it in
1442                            // `fetch::methods::fetch_with_cors_cache`
1443                            0
1444                        } else {
1445                            record.keep_alive_body_length
1446                        }
1447                    })
1448                    .sum()
1449            })
1450            .unwrap_or_default();
1451        // Step 8.10.5. If the sum of contentLength and inflightKeepaliveBytes is greater than 64 kibibytes, then return a network error.
1452        if content_length + in_flight_keep_alive_bytes > 64 * 1024 {
1453            return Response::network_error(NetworkError::TooManyInFlightKeepAliveRequests);
1454        }
1455    }
1456
1457    // Step 8.11: If httpRequest’s referrer is a URL, then:
1458    match http_request.referrer {
1459        Referrer::ReferrerUrl(ref http_request_referrer) |
1460        Referrer::Client(ref http_request_referrer) => {
1461            // Step 8.11.1: Let referrerValue be httpRequest’s referrer, serialized and isomorphic
1462            // encoded.
1463            if let Ok(referer) = http_request_referrer.as_str().parse::<Referer>() {
1464                // Step 8.11.2: Append (`Referer`, referrerValue) to httpRequest’s header list.
1465                http_request.headers.typed_insert(referer);
1466            } else {
1467                // This error should only happen in cases where hyper and rust-url disagree
1468                // about how to parse a referer.
1469                // https://github.com/servo/servo/issues/24175
1470                error!("Failed to parse {} as referrer", http_request_referrer);
1471            }
1472        },
1473        _ => {},
1474    };
1475
1476    // Step 8.12 Append a request `Origin` header for httpRequest.
1477    append_a_request_origin_header(http_request);
1478
1479    // Step 8.13 Append the Fetch metadata headers for httpRequest.
1480    append_the_fetch_metadata_headers(http_request);
1481
1482    // Step 8.14: If httpRequest’s initiator is "prefetch", then set a structured field value given
1483    // (`Sec-Purpose`, the token "prefetch") in httpRequest’s header list.
1484    if http_request.initiator == Initiator::Prefetch &&
1485        let Ok(value) = HeaderValue::from_str("prefetch")
1486    {
1487        http_request.headers.insert("Sec-Purpose", value);
1488    }
1489
1490    // Step 8.15: If httpRequest’s header list does not contain `User-Agent`, then user agents
1491    // should append (`User-Agent`, default `User-Agent` value) to httpRequest’s header list.
1492    if !http_request.headers.contains_key(header::USER_AGENT) {
1493        http_request
1494            .headers
1495            .typed_insert::<UserAgent>(context.user_agent.parse().unwrap());
1496    }
1497
1498    // Steps 8.16 to 8.18
1499    append_cache_data_to_headers(http_request);
1500
1501    // Step 8.19: If httpRequest’s header list contains `Range`, then append (`Accept-Encoding`,
1502    // `identity`) to httpRequest’s header list.
1503    if http_request.headers.contains_key(header::RANGE) &&
1504        let Ok(value) = HeaderValue::from_str("identity")
1505    {
1506        http_request.headers.insert("Accept-Encoding", value);
1507    }
1508
1509    // Step 8.20: Modify httpRequest’s header list per HTTP. Do not append a given header if
1510    // httpRequest’s header list contains that header’s name.
1511    // `Accept`, `Accept-Charset`, and `Accept-Language` must not be included at this point.
1512    http_request.headers.remove(header::HOST);
1513    // unlike http_loader, we should not set the accept header here
1514    set_default_accept_encoding(&mut http_request.headers);
1515
1516    let current_url = http_request.current_url();
1517
1518    // Step 8.21: If includeCredentials is true, then:
1519    // TODO some of this step can't be implemented yet
1520    if include_credentials {
1521        // Substep 1
1522        // TODO http://mxr.mozilla.org/servo/source/components/net/http_loader.rs#504
1523        // XXXManishearth http_loader has block_cookies: support content blocking here too
1524        set_request_cookies(
1525            &current_url,
1526            &mut http_request.headers,
1527            &context.state.cookie_jar,
1528        );
1529        // Substep 2
1530        if !http_request.headers.contains_key(header::AUTHORIZATION) {
1531            // Substep 3
1532            let mut authorization_value = None;
1533
1534            // Substep 4
1535            if let Some(basic) = auth_from_cache(&context.state.auth_cache, &current_url.origin()) &&
1536                (!http_request.use_url_credentials || !has_credentials(&current_url))
1537            {
1538                authorization_value = Some(basic);
1539            }
1540
1541            // Substep 5
1542            if authentication_fetch_flag &&
1543                authorization_value.is_none() &&
1544                has_credentials(&current_url)
1545            {
1546                authorization_value = Some(Authorization::basic(
1547                    current_url.username(),
1548                    current_url.password().unwrap_or(""),
1549                ));
1550            }
1551
1552            // Substep 6
1553            if let Some(basic) = authorization_value {
1554                http_request.headers.typed_insert(basic);
1555            }
1556        }
1557    }
1558
1559    // TODO(#33616) Step 8.22 If there’s a proxy-authentication entry, use it as appropriate.
1560    let should_wait = {
1561        // Enter critical section on cache entry.
1562        let mut cache_guard = block_for_cache_ready(
1563            context,
1564            http_request,
1565            done_chan,
1566            &mut revalidating_flag,
1567            &mut response,
1568        )
1569        .await;
1570
1571        // TODO(#33616): Step 9. If aborted, then return the appropriate network error for fetchParams.
1572
1573        // Step 10. If response is null, then:
1574        if response.is_none() {
1575            // Step 10.1 If httpRequest’s cache mode is "only-if-cached", then return a network error.
1576            if http_request.cache_mode == CacheMode::OnlyIfCached {
1577                // Exit critical section of cache entry.
1578                return Response::network_error(NetworkError::CacheError);
1579            }
1580
1581            // Step 10.2 Let forwardResponse be the result of running HTTP-network fetch given httpFetchParams,
1582            // includeCredentials, and isNewConnectionFetch.
1583            drop(cache_guard);
1584            let forward_response =
1585                http_network_fetch(http_fetch_params, include_credentials, done_chan, context)
1586                    .await;
1587
1588            let http_request = &mut http_fetch_params.request;
1589            let request_key = CacheKey::new(http_request);
1590            cache_guard = context
1591                .state
1592                .http_cache
1593                .get_or_guard(request_key.clone())
1594                .await;
1595            // Step 10.3 If httpRequest’s method is unsafe and forwardResponse’s status is in the range 200 to 399,
1596            // inclusive, invalidate appropriate stored responses in httpCache, as per the
1597            // "Invalidating Stored Responses" chapter of HTTP Caching, and set storedResponse to null.
1598            if forward_response.status.in_range(200..=399) && !http_request.method.is_safe() {
1599                if let Some(guard) = cache_guard.try_as_mut() {
1600                    invalidate_cached_resources(guard);
1601                }
1602                context
1603                    .state
1604                    .http_cache
1605                    .invalidate_related_urls(http_request, &forward_response, &request_key)
1606                    .await;
1607            }
1608
1609            // Step 10.4 If the revalidatingFlag is set and forwardResponse’s status is 304, then:
1610            if revalidating_flag && forward_response.status == StatusCode::NOT_MODIFIED {
1611                // Ensure done_chan is None,
1612                // since the network response will be replaced by the revalidated stored one.
1613                *done_chan = None;
1614                if let Some(guard) = cache_guard.try_as_mut() {
1615                    response = refresh(http_request, forward_response.clone(), done_chan, guard);
1616                }
1617
1618                if let Some(response) = &mut response {
1619                    response.cache_state = CacheState::Validated;
1620                }
1621            }
1622
1623            // Step 10.5 If response is null, then:
1624            if response.is_none() {
1625                // Step 10.5.1 Set response to forwardResponse.
1626                let forward_response = response.insert(forward_response);
1627
1628                // Per https://httpwg.org/specs/rfc9111.html#response.cacheability we must not cache responses
1629                // if the No-Store directive is present
1630                if http_request.cache_mode != CacheMode::NoStore {
1631                    // Step 10.5.2 Store httpRequest and forwardResponse in httpCache, as per the
1632                    //             "Storing Responses in Caches" chapter of HTTP Caching.
1633                    cache_guard.insert(http_request, forward_response);
1634                }
1635            }
1636            false
1637        } else {
1638            true
1639        }
1640    }; // Exit Critical Section on cache entry
1641
1642    if should_wait {
1643        // If the cache constructed a response, and that is still receiving from the network,
1644        // we must wait for it to finish in case it is still receiving from the network.
1645        // Note: this means only the fetch from which the original network response originated
1646        // will be able to stream it; all others receive a cached response in one chunk.
1647        wait_for_inflight_requests(done_chan, &mut response).await;
1648    }
1649
1650    let http_request = &mut http_fetch_params.request;
1651    let mut response = response.unwrap();
1652
1653    // Step 11. Set response’s URL list to a clone of httpRequest’s URL list.
1654    response.url_list = http_request
1655        .url_list
1656        .iter()
1657        .map(|claimed_url| claimed_url.url())
1658        .collect();
1659
1660    // Step 12. If httpRequest’s header list contains `Range`, then set response’s range-requested flag.
1661    if http_request.headers.contains_key(RANGE) {
1662        response.range_requested = true;
1663    }
1664
1665    // Step 13. Set response’s request-includes-credentials to includeCredentials.
1666    response.request_includes_credentials = include_credentials;
1667
1668    // Step 14. If response’s status is 401, httpRequest’s response tainting is not "cors",
1669    // includeCredentials is true, and request’s window is an environment settings object, then:
1670    // TODO(#33616): Figure out what to do with request window objects
1671    // NOTE: Requiring a WWW-Authenticate header here is ad-hoc, but seems to match what other browsers are
1672    // doing. See Step 14.1.
1673    if response.status.try_code() == Some(StatusCode::UNAUTHORIZED) &&
1674        !cors_flag &&
1675        include_credentials &&
1676        response.headers.contains_key(WWW_AUTHENTICATE)
1677    {
1678        // TODO: Step 14.1 Spec says requires testing on multiple WWW-Authenticate headers
1679
1680        let request = &mut fetch_params.request;
1681
1682        // Step 14.2 If request’s body is non-null, then:
1683        // “If request’s body’s source is null, then return a network error.”
1684        if request
1685            .body
1686            .as_ref()
1687            .is_some_and(|body| body.source_is_null())
1688        {
1689            return Response::network_error(NetworkError::ConnectionFailure);
1690        }
1691
1692        // Step 14.3 If request’s use-URL-credentials flag is unset or isAuthenticationFetch is true, then:
1693        if !request.use_url_credentials || authentication_fetch_flag {
1694            let Some(credentials) = context
1695                .state
1696                .request_authentication(request, &response)
1697                .await
1698            else {
1699                return response;
1700            };
1701
1702            if let Err(err) = request
1703                .current_url_mut()
1704                .set_username(&credentials.username)
1705            {
1706                error!("error setting username for url: {:?}", err);
1707                return response;
1708            };
1709
1710            if let Err(err) = request
1711                .current_url_mut()
1712                .set_password(Some(&credentials.password))
1713            {
1714                error!("error setting password for url: {:?}", err);
1715                return response;
1716            };
1717        }
1718
1719        // Make sure this is set to None,
1720        // since we're about to start a new `http_network_or_cache_fetch`.
1721        *done_chan = None;
1722
1723        // Step 14.4 Set response to the result of running HTTP-network-or-cache fetch given fetchParams and true.
1724        response = http_network_or_cache_fetch(
1725            fetch_params,
1726            true, /* authentication flag */
1727            cors_flag,
1728            done_chan,
1729            context,
1730        )
1731        .await;
1732    }
1733
1734    // Step 15. If response’s status is 407, then:
1735    if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED {
1736        let request = &mut fetch_params.request;
1737        // Step 15.1 If request’s traversable for user prompts is "no-traversable", then return a network error.
1738
1739        if request.traversable_for_user_prompts == TraversableForUserPrompts::NoTraversable {
1740            return Response::network_error(NetworkError::ResourceLoadError(
1741                "Can't find Window object".into(),
1742            ));
1743        }
1744
1745        // (Step 15.2 does not exist, requires testing on Proxy-Authenticate headers)
1746
1747        // TODO(#33616): Step 15.3 If fetchParams is canceled, then return
1748        // the appropriate network error for fetchParams.
1749
1750        // Step 15.4 Prompt the end user as appropriate in request’s window
1751        // window and store the result as a proxy-authentication entry.
1752        let Some(credentials) = context
1753            .state
1754            .request_authentication(request, &response)
1755            .await
1756        else {
1757            return response;
1758        };
1759
1760        // Store the credentials as a proxy-authentication entry.
1761        let entry = AuthCacheEntry {
1762            user_name: credentials.username,
1763            password: credentials.password,
1764        };
1765        {
1766            let mut auth_cache = context.state.auth_cache.write();
1767            let key = request
1768                .current_url()
1769                .origin()
1770                .ascii_serialization()
1771                .into_owned();
1772            auth_cache.entries.insert(key, entry);
1773        }
1774
1775        // Make sure this is set to None,
1776        // since we're about to start a new `http_network_or_cache_fetch`.
1777        *done_chan = None;
1778
1779        // Step 15.5 Set response to the result of running HTTP-network-or-cache fetch given fetchParams.
1780        response = http_network_or_cache_fetch(
1781            fetch_params,
1782            false, /* authentication flag */
1783            cors_flag,
1784            done_chan,
1785            context,
1786        )
1787        .await;
1788    }
1789
1790    // TODO(#33616): Step 16. If all of the following are true:
1791    // * response’s status is 421
1792    // * isNewConnectionFetch is false
1793    // * request’s body is null, or request’s body is non-null and request’s body’s source is non-null
1794    // then: [..]
1795
1796    // Step 17. If isAuthenticationFetch is true, then create an authentication entry for request and the given realm.
1797    if authentication_fetch_flag {
1798        // TODO(#33616)
1799    }
1800
1801    // Step 18. Return response.
1802    response
1803}
1804
1805/// If the cache is not ready to construct a response, wait.
1806///
1807/// The cache is not ready if a previous fetch checked the cache, found nothing,
1808/// and moved on to a network fetch, and hasn't updated the cache yet with a pending resource.
1809///
1810/// Note that this is a different workflow from the one involving `wait_for_cached_response`.
1811/// That one happens when a fetch gets a cache hit, and the resource is pending completion from the network.
1812#[servo_tracing::instrument(skip_all)]
1813async fn block_for_cache_ready<'a>(
1814    context: &'a FetchContext,
1815    http_request: &mut Request,
1816    done_chan: &mut DoneChannel,
1817    revalidating_flag: &mut bool,
1818    response: &mut Option<Response>,
1819) -> CachedResourcesOrGuard<'a> {
1820    let entry_key = CacheKey::new(http_request);
1821    let guard_result = context.state.http_cache.get_or_guard(entry_key).await;
1822
1823    match guard_result {
1824        CachedResourcesOrGuard::Guard(_) => {
1825            *done_chan = None;
1826        },
1827        CachedResourcesOrGuard::Value(ref cached_resources) => {
1828            // TODO(#33616): Step 8.23 Set httpCache to the result of determining the
1829            // HTTP cache partition, given httpRequest.
1830            // Step 8.25.1 Set storedResponse to the result of selecting a response from the httpCache,
1831            //              possibly needing validation, as per the "Constructing Responses from Caches"
1832            //              chapter of HTTP Caching, if any.
1833            let stored_response = construct_response(http_request, done_chan, cached_resources);
1834            // Step 8.25.2 If storedResponse is non-null, then:
1835            if let Some(response_from_cache) = stored_response {
1836                let response_headers = response_from_cache.response.headers.clone();
1837                let validation_status = response_from_cache.validation_status;
1838                let revalidation_guard = response_from_cache.revalidation_guard.clone();
1839
1840                // Substep 1, 2, 3, 4
1841                let (cached_response, needs_synchronous_revalidation) =
1842                    match (http_request.cache_mode, &http_request.mode) {
1843                        (CacheMode::ForceCache, _) => (Some(response_from_cache.response), false),
1844                        (CacheMode::OnlyIfCached, &RequestMode::SameOrigin) => {
1845                            (Some(response_from_cache.response), false)
1846                        },
1847                        (CacheMode::OnlyIfCached, _) |
1848                        (CacheMode::NoStore, _) |
1849                        (CacheMode::Reload, _) => (None, false),
1850                        (_, _) => (
1851                            Some(response_from_cache.response),
1852                            validation_status ==
1853                                (ValidationStatus::Stale {
1854                                    revalidate_in_background: false,
1855                                }),
1856                        ),
1857                    };
1858
1859                if needs_synchronous_revalidation {
1860                    *revalidating_flag = true;
1861                    // Substep 5
1862                    if let Some(http_date) = response_headers.typed_get::<LastModified>() {
1863                        let http_date: SystemTime = http_date.into();
1864                        http_request
1865                            .headers
1866                            .typed_insert(IfModifiedSince::from(http_date));
1867                    }
1868                    if let Some(entity_tag) = response_headers.get(header::ETAG) {
1869                        http_request
1870                            .headers
1871                            .insert(header::IF_NONE_MATCH, entity_tag.clone());
1872                    }
1873                } else {
1874                    // Substep 6
1875                    // If it's a stale-while-revalidate response, also refresh it in the background.
1876                    let revalidate_in_background = validation_status ==
1877                        (ValidationStatus::Stale {
1878                            revalidate_in_background: true,
1879                        });
1880                    if revalidate_in_background && cached_response.is_some() {
1881                        spawn_stale_while_revalidate(context, http_request, revalidation_guard);
1882                    }
1883                    *response = cached_response;
1884                    if let Some(response) = response {
1885                        response.cache_state = CacheState::Local;
1886                    }
1887                }
1888                if response.is_none() {
1889                    // Ensure the done chan is not set if we're not using the cached response,
1890                    // as the cache might have set it to Some if it constructed a pending response.
1891                    *done_chan = None;
1892                }
1893            }
1894        },
1895    }
1896    guard_result
1897}
1898
1899/// The cached (stale) response has already been returned to the caller; here we
1900/// fire off an independent fetch whose only purpose is to refresh the stored response.
1901fn spawn_stale_while_revalidate(
1902    context: &FetchContext,
1903    http_request: &Request,
1904    revalidation_guard: StdArc<AtomicBool>,
1905) {
1906    // Only proceed if we are the one who flips the guard from `false` to `true`.
1907    if revalidation_guard
1908        .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
1909        .is_err()
1910    {
1911        return;
1912    }
1913
1914    // By setting `CacheMode::NoCache` to the cloned request,
1915    // we ensure that the background revalidation fetch will always go to the network, and preventing an inifinite loop.
1916    let mut revalidation_request = http_request.clone();
1917    revalidation_request.cache_mode = CacheMode::NoCache;
1918
1919    // A background revalidation must not itself spin up service workers
1920    revalidation_request.service_workers_mode = ServiceWorkersMode::None;
1921
1922    let context = context.clone();
1923    debug!(
1924        "spawning stale-while-revalidate background revalidation for {:?}",
1925        revalidation_request.current_url()
1926    );
1927    spawn_task(async move {
1928        let mut target = DiscardFetch;
1929
1930        let _ = fetch(revalidation_request, &mut target, &context).await;
1931        revalidation_guard.store(false, Ordering::Release);
1932    });
1933}
1934
1935/// Wait for a cached response from channel.
1936/// Happens when a fetch gets a cache hit, and the resource is pending completion from the network.
1937async fn wait_for_inflight_requests(done_chan: &mut DoneChannel, response: &mut Option<Response>) {
1938    if let Some(ref mut ch) = *done_chan {
1939        // The cache constructed a response with a body of ResponseBody::Receiving.
1940        // We wait for the response in the cache to "finish",
1941        // with a body of either Done or Cancelled.
1942        assert!(response.is_some());
1943
1944        loop {
1945            match ch.1.recv().await {
1946                Some(Data::ContentLength(_)) | Some(Data::Payload(_)) | Some(Data::Error(_)) => {},
1947                Some(Data::Done) => break, // Return the full response as if it was initially cached as such.
1948                Some(Data::Cancelled) => {
1949                    // The response was cancelled while the fetch was ongoing.
1950                    break;
1951                },
1952                None => panic!("HTTP cache should always send Done or Cancelled"),
1953            }
1954        }
1955    }
1956    // Set done_chan back to None, it's cache-related usefulness ends here.
1957    *done_chan = None;
1958}
1959
1960/// <https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check>
1961///
1962/// This is obtained from [cross_origin_resource_policy_check]
1963#[derive(PartialEq)]
1964enum CrossOriginResourcePolicy {
1965    Allowed,
1966    Blocked,
1967}
1968
1969enum ForNavigation {
1970    #[expect(dead_code)]
1971    Yes,
1972    No,
1973}
1974
1975/// <https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check>
1976fn cross_origin_resource_policy_check(
1977    origin: &Origin,
1978    request_client: &RequestClient,
1979    response: &Response,
1980    for_navigation: ForNavigation,
1981) -> CrossOriginResourcePolicy {
1982    // Step 1. Set forNavigation to false if it is not given.
1983    //
1984    // That's the default value of the enum
1985
1986    // Step 2. Let embedderPolicy be settingsObject’s policy container’s embedder policy.
1987    let embedder_policy = &request_client.policy_container.embedder_policy;
1988
1989    // Step 3. If the cross-origin resource policy internal check with origin, "unsafe-none",
1990    // response, and forNavigation returns blocked, then return blocked.
1991    if cross_origin_resource_policy_internal_check(
1992        origin,
1993        EmbedderPolicyValue::UnsafeNone,
1994        response,
1995        &for_navigation,
1996    ) == CrossOriginResourcePolicy::Blocked
1997    {
1998        return CrossOriginResourcePolicy::Blocked;
1999    }
2000
2001    // TODO Step 4. If the cross-origin resource policy internal check with origin,
2002    // embedderPolicy’s report only value, response, and forNavigation returns blocked, then queue
2003    // a cross-origin embedder policy CORP violation report with response, settingsObject,
2004    // destination, and true.
2005
2006    // Step 5. If the cross-origin resource policy internal check with origin, embedderPolicy’s
2007    // value, response, and forNavigation returns allowed, then return allowed.
2008    if cross_origin_resource_policy_internal_check(
2009        origin,
2010        embedder_policy.value,
2011        response,
2012        &for_navigation,
2013    ) == CrossOriginResourcePolicy::Allowed
2014    {
2015        return CrossOriginResourcePolicy::Allowed;
2016    }
2017
2018    // TODO Step 6. Queue a cross-origin embedder policy CORP violation report with response,
2019    // settingsObject, destination, and false.
2020
2021    // Step 7. Return blocked.
2022    CrossOriginResourcePolicy::Blocked
2023}
2024
2025/// <https://fetch.spec.whatwg.org/#cross-origin-resource-policy-internal-check>
2026fn cross_origin_resource_policy_internal_check(
2027    origin: &Origin,
2028    embedder_policy_value: EmbedderPolicyValue,
2029    response: &Response,
2030    for_navigation: &ForNavigation,
2031) -> CrossOriginResourcePolicy {
2032    // Step 1. If forNavigation is true and embedderPolicyValue is "unsafe-none", then return allowed.
2033    if let ForNavigation::Yes = for_navigation &&
2034        let EmbedderPolicyValue::UnsafeNone = embedder_policy_value
2035    {
2036        return CrossOriginResourcePolicy::Allowed;
2037    }
2038
2039    // Step 2. Let policy be the result of getting `Cross-Origin-Resource-Policy` from response’s header list.
2040    let policy = response
2041        .headers
2042        .get(HeaderName::from_static("cross-origin-resource-policy"))
2043        .and_then(|h| h.to_str().ok());
2044
2045    // Step 3. If policy is neither `same-origin`, `same-site`, nor `cross-origin`, then set policy to null.
2046    let policy = policy
2047        .filter(|&s| s == "same-origin" || s == "same-site" || s == "cross-origin")
2048        // Step 4. If policy is null, then switch on embedderPolicyValue:
2049        .or(match embedder_policy_value {
2050            // Do nothing.
2051            EmbedderPolicyValue::UnsafeNone => None,
2052            // Set policy to `same-origin`.
2053            EmbedderPolicyValue::RequireCorp => Some("same-origin"),
2054        });
2055
2056    // Step 5. Switch on policy:
2057    match policy {
2058        Some("same-origin") => {
2059            // If origin is same origin with response’s URL’s origin, then return allowed.
2060            if let Origin::Origin(request_origin) = origin &&
2061                response
2062                    .url()
2063                    .is_some_and(|url| request_origin == &url.origin())
2064            {
2065                return CrossOriginResourcePolicy::Allowed;
2066            }
2067
2068            // Otherwise, return blocked.
2069            CrossOriginResourcePolicy::Blocked
2070        },
2071        Some("same-site") => {
2072            if let Some(response_url) = response.url() {
2073                // If all of the following are true
2074                // origin is schemelessly same site with response’s URL’s origin
2075                // origin’s scheme is "https" or response’s URL’s scheme is not "https"
2076                if let Origin::Origin(request_origin) = origin &&
2077                    is_schemelessy_same_site(request_origin, &response_url.origin()) &&
2078                    (request_origin.scheme() == Some("https") ||
2079                        response_url.scheme() != "https")
2080                {
2081                    return CrossOriginResourcePolicy::Allowed;
2082                }
2083            }
2084            // Otherwise, return blocked.
2085            CrossOriginResourcePolicy::Blocked
2086        },
2087        // null / 'cross-origin'
2088        // Return allowed.
2089        _ => CrossOriginResourcePolicy::Allowed,
2090    }
2091}
2092
2093// Convenience struct that implements Done, for setting responseEnd on function return
2094struct ResponseEndTimer(Option<ResourceFetchTimingContainer>);
2095
2096impl ResponseEndTimer {
2097    fn neuter(&mut self) {
2098        self.0 = None;
2099    }
2100}
2101
2102impl Drop for ResponseEndTimer {
2103    fn drop(&mut self) {
2104        let ResponseEndTimer(resource_fetch_timing_opt) = self;
2105
2106        resource_fetch_timing_opt.as_ref().map_or((), |t| {
2107            t.set_attribute(ResourceAttribute::ResponseEnd);
2108        })
2109    }
2110}
2111
2112/// [HTTP network fetch](https://fetch.spec.whatwg.org/#http-network-fetch)
2113#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
2114async fn http_network_fetch(
2115    fetch_params: &mut FetchParams,
2116    credentials_flag: bool,
2117    done_chan: &mut DoneChannel,
2118    context: &FetchContext,
2119) -> Response {
2120    let mut response_end_timer = ResponseEndTimer(Some(context.timing.clone()));
2121
2122    // Step 1: Let request be fetchParams’s request.
2123    let request = &mut fetch_params.request;
2124
2125    // TODO Step 2. If request’s client is offline, then return a network error.
2126
2127    // TODO Step 3. Let response be null.
2128
2129    // TODO Step 4. Let timingInfo be fetchParams’s timing info.
2130
2131    // TODO Step 5. Let networkPartitionKey be the result of determining the network partition key
2132    // given request.
2133
2134    let url = request.current_url();
2135    let request_id = request.id.0.to_string();
2136    if log_enabled!(log::Level::Info) {
2137        info!("{:?} request for {}", request.method, url);
2138        for header in request.headers.iter() {
2139            debug!(" - {:?}", header);
2140        }
2141    }
2142
2143    // XHR uses the default destination; other kinds of fetches (which haven't been implemented yet)
2144    // do not. Once we support other kinds of fetches we'll need to be more fine grained here
2145    // since things like image fetches are classified differently by devtools
2146    let is_xhr = request.destination == Destination::None;
2147
2148    // The receiver will receive true if there has been an error streaming the request body.
2149    let (fetch_terminated_sender, mut fetch_terminated_receiver) = unbounded_channel();
2150
2151    let body = request.body.as_ref().map(|body| body.clone_stream());
2152
2153    if body.is_none() {
2154        // There cannot be an error streaming a non-existent body.
2155        // However in such a case the channel will remain unused
2156        // and drop inside `obtain_response`.
2157        // Send the confirmation now, ensuring the receiver will not dis-connect first.
2158        let _ = fetch_terminated_sender.send(false);
2159    }
2160
2161    let browsing_context_id = request.target_webview_id.map(Into::into);
2162
2163    // Step 6. Let newConnection be "yes" if forceNewConnection is true; otherwise "no".
2164
2165    // Step 7. Switch on request’s mode:
2166    let (res, msg) = match &request.mode {
2167        // Let connection be the result of obtaining a WebSocket connection, given request’s current URL.
2168        RequestMode::WebSocket {
2169            protocols,
2170            original_url: _,
2171        } => {
2172            // https://fetch.spec.whatwg.org/#websocket-opening-handshake
2173
2174            let (resource_event_sender, dom_action_receiver) = {
2175                let mut websocket_chan = context.websocket_chan.as_ref().unwrap().lock();
2176                (
2177                    websocket_chan.sender.clone(),
2178                    websocket_chan.receiver.take().unwrap(),
2179                )
2180            };
2181
2182            let mut tls_config = create_tls_config(
2183                context.ca_certificates.clone(),
2184                context.ignore_certificate_errors,
2185                context.state.override_manager.clone(),
2186            );
2187            tls_config.alpn_protocols = vec!["http/1.1".to_string().into()];
2188
2189            let response = match start_websocket(
2190                context.state.clone(),
2191                resource_event_sender,
2192                protocols,
2193                request,
2194                tls_config,
2195                dom_action_receiver,
2196            )
2197            .await
2198            {
2199                Ok(response) => response,
2200                Err(error) => {
2201                    return Response::network_error(NetworkError::WebsocketConnectionFailure(
2202                        format!("{error:?}"),
2203                    ));
2204                },
2205            };
2206
2207            let response = response.map(|r| match r {
2208                Some(body) => Full::from(body).map_err(|_| unreachable!()).boxed(),
2209                None => http_body_util::Empty::new()
2210                    .map_err(|_| unreachable!())
2211                    .boxed(),
2212            });
2213            (Decoder::detect(response, url.is_secure_scheme()), None)
2214        },
2215        // Let connection be the result of obtaining a connection, given networkPartitionKey,
2216        // request’s current URL, includeCredentials, and newConnection.
2217        _ => {
2218            let response_future = obtain_response(
2219                &context.state.client,
2220                &url,
2221                &request.method,
2222                &mut request.headers,
2223                body,
2224                request
2225                    .body
2226                    .as_ref()
2227                    .is_some_and(|body| body.source_is_null()),
2228                &request.pipeline_id,
2229                Some(&request_id),
2230                request.destination,
2231                is_xhr,
2232                context,
2233                fetch_terminated_sender,
2234                browsing_context_id,
2235            );
2236
2237            // This will only get the headers, the body is read later
2238            let (res, msg) = match response_future.await {
2239                Ok(wrapped_response) => wrapped_response,
2240                Err(error) => return Response::network_error(error),
2241            };
2242            (res, msg)
2243        },
2244    };
2245
2246    if log_enabled!(log::Level::Info) {
2247        debug!("{:?} response for {}", res.version(), url);
2248        for header in res.headers().iter() {
2249            debug!(" - {:?}", header);
2250        }
2251    }
2252
2253    // Check if there was an error while streaming the request body.
2254    //
2255    match fetch_terminated_receiver.recv().await {
2256        Some(true) => return Response::network_error(NetworkError::ConnectionFailure),
2257        Some(false) => {},
2258        _ => warn!("Failed to receive confirmation request was streamed without error."),
2259    }
2260
2261    let timing = context.timing.inner().clone();
2262    let mut response = Response::new(url.clone(), timing);
2263
2264    if let Some(handshake_info) = res.extensions().get::<TlsHandshakeInfo>() {
2265        let mut hsts_enabled = url
2266            .host_str()
2267            .is_some_and(|host| context.state.hsts_list.read().is_host_secure(host));
2268
2269        if url.scheme() == "https" &&
2270            let Some(sts) = res.headers().typed_get::<StrictTransportSecurity>()
2271        {
2272            // max-age > 0 enables HSTS, max-age = 0 disables it (RFC 6797 Section 6.1.1)
2273            hsts_enabled = sts.max_age().as_secs() > 0;
2274        }
2275        response.tls_security_info = Some(build_tls_security_info(handshake_info, hsts_enabled));
2276    }
2277
2278    let status_text = res
2279        .extensions()
2280        .get::<ReasonPhrase>()
2281        .map(ReasonPhrase::as_bytes)
2282        .or_else(|| res.status().canonical_reason().map(str::as_bytes))
2283        .map(Vec::from)
2284        .unwrap_or_default();
2285    response.status = HttpStatus::new(res.status(), status_text);
2286
2287    info!("got {:?} response for {:?}", res.status(), request.url());
2288    response.headers = res.headers().clone();
2289    response.referrer = request.referrer.to_url().cloned();
2290    response.referrer_policy = request.referrer_policy;
2291
2292    let res_body = response.body.clone();
2293
2294    // We're about to spawn a future to be waited on here
2295    let (done_sender, done_receiver) = unbounded_channel();
2296    *done_chan = Some((done_sender.clone(), done_receiver));
2297
2298    let devtools_sender = context.devtools_chan.clone();
2299    let cancellation_listener = context.cancellation_listener.clone();
2300    if cancellation_listener.cancelled() {
2301        return Response::network_error(NetworkError::LoadCancelled);
2302    }
2303
2304    *res_body.lock() = ResponseBody::Receiving(vec![]);
2305    let res_body2 = res_body.clone();
2306
2307    if let Some(ref sender) = devtools_sender &&
2308        let Some(m) = msg
2309    {
2310        send_request_to_devtools(m, sender);
2311    }
2312
2313    let done_sender2 = done_sender.clone();
2314    let done_sender3 = done_sender.clone();
2315    let timing_ptr2 = context.timing.clone();
2316    let timing_ptr3 = context.timing.clone();
2317    let devtools_request = request.clone();
2318    let url1 = devtools_request.url();
2319    let url2 = url1.clone();
2320
2321    let status = response.status.clone();
2322    let headers = response.headers.clone();
2323    let devtools_chan = context.devtools_chan.clone();
2324
2325    if let Some(possible_length) = res
2326        .headers()
2327        .get(http::header::CONTENT_LENGTH)
2328        .and_then(|header_value| header_value.to_str().ok())
2329        .and_then(|s| s.parse().ok())
2330        .map(|length| min(length, pref!(network_max_content_length) as usize))
2331    {
2332        let _ = done_sender.send(Data::ContentLength(possible_length));
2333    }
2334
2335    spawn_task(
2336        res.into_body()
2337            .try_fold(res_body, move |res_body, chunk| {
2338                if cancellation_listener.cancelled() {
2339                    *res_body.lock() = ResponseBody::Done(vec![]);
2340                    let _ = done_sender.send(Data::Cancelled);
2341                    return future::ready(Err(std::io::Error::new(
2342                        std::io::ErrorKind::Interrupted,
2343                        "Fetch aborted",
2344                    )));
2345                }
2346                if let ResponseBody::Receiving(ref mut body) = *res_body.lock() {
2347                    let bytes = chunk;
2348                    body.extend_from_slice(&bytes);
2349                    let _ = done_sender.send(Data::Payload(bytes.to_vec()));
2350                }
2351                future::ready(Ok(res_body))
2352            })
2353            .and_then(move |res_body| {
2354                debug!("successfully finished response for {:?}", url1);
2355                let mut body = res_body.lock();
2356                let completed_body = match *body {
2357                    ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2358                    _ => vec![],
2359                };
2360                let devtools_response_body = completed_body.clone();
2361                *body = ResponseBody::Done(completed_body);
2362                send_response_values_to_devtools(
2363                    Some(headers),
2364                    status,
2365                    Some(devtools_response_body),
2366                    CacheState::None,
2367                    &devtools_request,
2368                    devtools_chan,
2369                );
2370                timing_ptr2.set_attribute(ResourceAttribute::ResponseEnd);
2371                let _ = done_sender2.send(Data::Done);
2372                future::ready(Ok(()))
2373            })
2374            .map_err(move |error| {
2375                if let std::io::ErrorKind::InvalidData = error.kind() {
2376                    debug!("Content decompression error for {:?}", url2);
2377                    let _ = done_sender3.send(Data::Error(NetworkError::DecompressionError));
2378                    let mut body = res_body2.lock();
2379
2380                    *body = ResponseBody::Done(vec![]);
2381                }
2382                debug!("finished response for {:?}", url2);
2383                let mut body = res_body2.lock();
2384                let completed_body = match *body {
2385                    ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2386                    _ => vec![],
2387                };
2388                *body = ResponseBody::Done(completed_body);
2389                timing_ptr3.set_attribute(ResourceAttribute::ResponseEnd);
2390                let _ = done_sender3.send(Data::Done);
2391            }),
2392    );
2393
2394    // TODO these substeps aren't possible yet
2395    // Substep 1
2396
2397    // Substep 2
2398
2399    // TODO Read request
2400
2401    // Step 6-11
2402    // (needs stream bodies)
2403
2404    // Step 13
2405    // TODO this step isn't possible yet (CSP)
2406
2407    // Step 14, update the cached response, done via the shared response body.
2408
2409    // TODO this step isn't possible yet
2410    // Step 15
2411    if credentials_flag {
2412        set_cookies_from_headers(&url, &response.headers, &context.state.cookie_jar);
2413    }
2414    context
2415        .state
2416        .hsts_list
2417        .write()
2418        .update_hsts_list_from_response(&url, &response.headers);
2419
2420    // TODO these steps
2421    // Step 16
2422    // Substep 1
2423    // Substep 2
2424    // Sub-substep 1
2425    // Sub-substep 2
2426    // Sub-substep 3
2427    // Sub-substep 4
2428    // Substep 3
2429
2430    // Step 16
2431
2432    // Ensure we don't override "responseEnd" on successful return of this function
2433    response_end_timer.neuter();
2434    response
2435}
2436
2437/// [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch)
2438async fn cors_preflight_fetch(
2439    request: &Request,
2440    cache: &mut CorsCache,
2441    context: &FetchContext,
2442) -> Response {
2443    // Step 1. Let preflight be a new request whose method is `OPTIONS`, URL list is a clone
2444    // of request’s URL list, initiator is request’s initiator, destination is request’s destination,
2445    // origin is request’s origin, referrer is request’s referrer, referrer policy is request’s
2446    // referrer policy, mode is "cors", and response tainting is "cors".
2447    let mut preflight = RequestBuilder::new(
2448        request.target_webview_id,
2449        request.current_url_with_blob_claim(),
2450        request.referrer.clone(),
2451    )
2452    .method(Method::OPTIONS)
2453    .origin(match &request.origin {
2454        Origin::Client => {
2455            unreachable!("We shouldn't get Client origin in cors_preflight_fetch.")
2456        },
2457        Origin::Origin(origin) => origin.clone(),
2458    })
2459    .pipeline_id(request.pipeline_id)
2460    .initiator(request.initiator)
2461    .destination(request.destination)
2462    .referrer_policy(request.referrer_policy)
2463    .mode(RequestMode::CorsMode)
2464    .response_tainting(ResponseTainting::CorsTainting)
2465    .policy_container(match &request.policy_container {
2466        RequestPolicyContainer::Client => {
2467            unreachable!("We should have a policy container for request in cors_preflight_fetch")
2468        },
2469        RequestPolicyContainer::PolicyContainer(policy_container) => policy_container.clone(),
2470    })
2471    .url_list(
2472        request
2473            .url_list
2474            .iter()
2475            .map(|claimed_url| claimed_url.url())
2476            .collect(),
2477    )
2478    .build();
2479
2480    // Step 2. Append (`Accept`, `*/*`) to preflight’s header list.
2481    preflight
2482        .headers
2483        .insert(ACCEPT, HeaderValue::from_static("*/*"));
2484
2485    // Step 3. Append (`Access-Control-Request-Method`, request’s method) to preflight’s header list.
2486    preflight
2487        .headers
2488        .typed_insert::<AccessControlRequestMethod>(AccessControlRequestMethod::from(
2489            request.method.clone(),
2490        ));
2491
2492    // Step 4. Let headers be the CORS-unsafe request-header names with request’s header list.
2493    let headers = get_cors_unsafe_header_names(&request.headers);
2494
2495    // Step 5 If headers is not empty, then:
2496    if !headers.is_empty() {
2497        // 5.1 Let value be the items in headers separated from each other by `,`
2498        // TODO(36451): replace this with typed_insert when headers fixes headers#207
2499        preflight.headers.insert(
2500            ACCESS_CONTROL_REQUEST_HEADERS,
2501            HeaderValue::from_bytes(itertools::join(headers.iter(), ",").as_bytes())
2502                .unwrap_or(HeaderValue::from_static("")),
2503        );
2504    }
2505
2506    // Step 6. Let response be the result of running HTTP-network-or-cache fetch given a
2507    // new fetch params whose request is preflight.
2508    let mut fetch_params = FetchParams::new(preflight);
2509    let response =
2510        http_network_or_cache_fetch(&mut fetch_params, false, false, &mut None, context).await;
2511
2512    // Step 7. If a CORS check for request and response returns success and response’s status is an ok status, then:
2513    if cors_check(request, &response).is_ok() && response.status.code().is_success() {
2514        // Step 7.1 Let methods be the result of extracting header list values given
2515        // `Access-Control-Allow-Methods` and response’s header list.
2516        let mut methods = if response
2517            .headers
2518            .contains_key(header::ACCESS_CONTROL_ALLOW_METHODS)
2519        {
2520            match response.headers.typed_get::<AccessControlAllowMethods>() {
2521                Some(methods) => methods.iter().collect(),
2522                // Step 7.3 If either methods or headerNames is failure, return a network error.
2523                None => {
2524                    return Response::network_error(NetworkError::CorsAllowMethods);
2525                },
2526            }
2527        } else {
2528            vec![]
2529        };
2530
2531        // Step 7.2 Let headerNames be the result of extracting header list values given
2532        // `Access-Control-Allow-Headers` and response’s header list.
2533        let header_names = if response
2534            .headers
2535            .contains_key(header::ACCESS_CONTROL_ALLOW_HEADERS)
2536        {
2537            match response.headers.typed_get::<AccessControlAllowHeaders>() {
2538                Some(names) => names.iter().collect(),
2539                // Step 7.3 If either methods or headerNames is failure, return a network error.
2540                None => {
2541                    return Response::network_error(NetworkError::CorsAllowHeaders);
2542                },
2543            }
2544        } else {
2545            vec![]
2546        };
2547
2548        debug!(
2549            "CORS check: Allowed methods: {:?}, current method: {:?}",
2550            methods, request.method
2551        );
2552
2553        // Step 7.4 If methods is null and request’s use-CORS-preflight flag is set,
2554        // then set methods to a new list containing request’s method.
2555        if methods.is_empty() && request.use_cors_preflight {
2556            methods = vec![request.method.clone()];
2557        }
2558
2559        // Step 7.5 If request’s method is not in methods, request’s method is not a CORS-safelisted method,
2560        // and request’s credentials mode is "include" or methods does not contain `*`, then return a network error.
2561        if methods
2562            .iter()
2563            .all(|method| *method.as_str() != *request.method.as_ref()) &&
2564            !is_cors_safelisted_method(&request.method) &&
2565            (request.credentials_mode == CredentialsMode::Include ||
2566                methods.iter().all(|method| method.as_ref() != "*"))
2567        {
2568            return Response::network_error(NetworkError::CorsMethod);
2569        }
2570
2571        debug!(
2572            "CORS check: Allowed headers: {:?}, current headers: {:?}",
2573            header_names, request.headers
2574        );
2575
2576        // Step 7.6 If one of request’s header list’s names is a CORS non-wildcard request-header name
2577        // and is not a byte-case-insensitive match for an item in headerNames, then return a network error.
2578        if request.headers.iter().any(|(name, _)| {
2579            is_cors_non_wildcard_request_header_name(name) &&
2580                header_names.iter().all(|header_name| header_name != name)
2581        }) {
2582            return Response::network_error(NetworkError::CorsAuthorization);
2583        }
2584
2585        // Step 7.7 For each unsafeName of the CORS-unsafe request-header names with request’s header list,
2586        // if unsafeName is not a byte-case-insensitive match for an item in headerNames and request’s credentials
2587        // mode is "include" or headerNames does not contain `*`, return a network error.
2588        let unsafe_names = get_cors_unsafe_header_names(&request.headers);
2589        let header_names_set: HashSet<&HeaderName> = HashSet::from_iter(header_names.iter());
2590        let header_names_contains_star = header_names
2591            .iter()
2592            .any(|header_name| header_name.as_str() == "*");
2593        for unsafe_name in unsafe_names.iter() {
2594            if !header_names_set.contains(unsafe_name) &&
2595                (request.credentials_mode == CredentialsMode::Include ||
2596                    !header_names_contains_star)
2597            {
2598                return Response::network_error(NetworkError::CorsHeaders);
2599            }
2600        }
2601
2602        // Step 7.8 Let max-age be the result of extracting header list values given
2603        // `Access-Control-Max-Age` and response’s header list.
2604        let max_age: Option<Duration> = response
2605            .headers
2606            .typed_get::<AccessControlMaxAge>()
2607            .map(|acma| acma.into());
2608
2609        // Step 7.9 If max-age is failure or null, then set max-age to 5.
2610        let max_age = max_age.unwrap_or(Duration::from_secs(5));
2611
2612        // Step 7.10 If max-age is greater than an imposed limit on max-age, then set max-age to the imposed limit.
2613        // TODO: Need to define what an imposed limit on max-age is
2614
2615        // Step 7.11 If the user agent does not provide for a cache, then return response.
2616        // NOTE: This can be ignored, we do have a CORS cache
2617
2618        // Step 7.12 For each method in methods for which there is a method cache entry match using request,
2619        // set matching entry’s max-age to max-age.
2620        // Step 7.13 For each method in methods for which there is no method cache entry match using request,
2621        // create a new cache entry with request, max-age, method, and null.
2622        for method in &methods {
2623            cache.match_method_and_update(request, method.clone(), max_age);
2624        }
2625
2626        // Step 7.14 For each headerName in headerNames for which there is a header-name cache entry match using request,
2627        // set matching entry’s max-age to max-age.
2628        // Step 7.15 For each headerName in headerNames for which there is no header-name cache entry match using request,
2629        // create a new cache entry with request, max-age, null, and headerName.
2630        for header_name in &header_names {
2631            cache.match_header_and_update(request, header_name, max_age);
2632        }
2633
2634        // Step 7.16 Return response.
2635        return response;
2636    }
2637
2638    // Step 8 Return a network error.
2639    Response::network_error(NetworkError::CorsGeneral)
2640}
2641
2642/// [CORS check](https://fetch.spec.whatwg.org#concept-cors-check)
2643fn cors_check(request: &Request, response: &Response) -> Result<(), ()> {
2644    // Step 1. Let origin be the result of getting `Access-Control-Allow-Origin` from response’s header list.
2645    let Some(origins) =
2646        get_value_from_header_list(ACCESS_CONTROL_ALLOW_ORIGIN.as_str(), &response.headers)
2647    else {
2648        // Step 2. If origin is null, then return failure.
2649        return Err(());
2650    };
2651    let origin = origins.into_iter().map(char::from).collect::<String>();
2652
2653    // Step 3. If request’s credentials mode is not "include" and origin is `*`, then return success.
2654    if request.credentials_mode != CredentialsMode::Include && origin == "*" {
2655        return Ok(());
2656    }
2657
2658    // Step 4. If the result of byte-serializing a request origin with request is not origin, then return failure.
2659    if serialize_request_origin(request).to_string() != origin {
2660        return Err(());
2661    }
2662
2663    // Step 5. If request’s credentials mode is not "include", then return success.
2664    if request.credentials_mode != CredentialsMode::Include {
2665        return Ok(());
2666    }
2667
2668    // Step 6. Let credentials be the result of getting `Access-Control-Allow-Credentials` from response’s header list.
2669    let credentials = response
2670        .headers
2671        .typed_get::<AccessControlAllowCredentials>();
2672
2673    // Step 7. If credentials is `true`, then return success.
2674    if credentials.is_some() {
2675        return Ok(());
2676    }
2677
2678    // Step 8. Return failure.
2679    Err(())
2680}
2681
2682fn has_credentials(url: &ServoUrl) -> bool {
2683    !url.username().is_empty() || url.password().is_some()
2684}
2685
2686fn is_no_store_cache(headers: &HeaderMap) -> bool {
2687    headers.contains_key(header::IF_MODIFIED_SINCE) |
2688        headers.contains_key(header::IF_NONE_MATCH) |
2689        headers.contains_key(header::IF_UNMODIFIED_SINCE) |
2690        headers.contains_key(header::IF_MATCH) |
2691        headers.contains_key(header::IF_RANGE)
2692}
2693
2694/// <https://fetch.spec.whatwg.org/#redirect-status>
2695fn is_redirect_status(status: StatusCode) -> bool {
2696    matches!(
2697        status,
2698        StatusCode::MOVED_PERMANENTLY |
2699            StatusCode::FOUND |
2700            StatusCode::SEE_OTHER |
2701            StatusCode::TEMPORARY_REDIRECT |
2702            StatusCode::PERMANENT_REDIRECT
2703    )
2704}
2705
2706/// <https://fetch.spec.whatwg.org/#serializing-a-request-origin>
2707fn serialize_request_origin(request: &Request) -> headers::Origin {
2708    // Step 1. Assert: request’s origin is not "client".
2709    let Origin::Origin(origin) = &request.origin else {
2710        panic!("origin cannot be \"client\" at this point in time");
2711    };
2712
2713    // Step 2. If request’s redirect-taint is not "same-origin", then return "null".
2714    if request.redirect_taint_for_request() != RedirectTaint::SameOrigin {
2715        return headers::Origin::NULL;
2716    }
2717
2718    // Step 3. Return request’s origin, serialized.
2719    serialize_origin(origin)
2720}
2721
2722/// Step 3 of <https://fetch.spec.whatwg.org/#serializing-a-request-origin>.
2723pub fn serialize_origin(origin: &ImmutableOrigin) -> headers::Origin {
2724    match origin {
2725        ImmutableOrigin::Opaque(_) => headers::Origin::NULL,
2726        ImmutableOrigin::Tuple(scheme, host, port) => {
2727            // Note: This must be kept in sync with `Origin::ascii_serialization()`, which does not
2728            // use the port number when a default port is used.
2729            let port = match (scheme.as_ref(), port) {
2730                ("http" | "ws", 80) | ("https" | "wss", 443) | ("ftp", 21) => None,
2731                _ => Some(*port),
2732            };
2733
2734            // TODO: Ensure that hyper/servo don't disagree about valid origin headers
2735            headers::Origin::try_from_parts(scheme, &host.to_string(), port)
2736                .unwrap_or(headers::Origin::NULL)
2737        },
2738    }
2739}
2740
2741/// <https://fetch.spec.whatwg.org/#append-a-request-origin-header>
2742#[expect(
2743    clippy::collapsible_match,
2744    reason = "The current way follows the spec more closely"
2745)]
2746fn append_a_request_origin_header(request: &mut Request) {
2747    // Step 1. Assert: request’s origin is not "client".
2748    let Origin::Origin(request_origin) = &request.origin else {
2749        panic!("origin cannot be \"client\" at this point in time");
2750    };
2751
2752    // Step 2. Let serializedOrigin be the result of byte-serializing a request origin with request.
2753    let mut serialized_origin = serialize_request_origin(request);
2754
2755    // Step 3. If request’s response tainting is "cors" or request’s mode is "websocket",
2756    //         then append (`Origin`, serializedOrigin) to request’s header list.
2757    if request.response_tainting == ResponseTainting::CorsTainting ||
2758        matches!(request.mode, RequestMode::WebSocket { .. })
2759    {
2760        request.headers.typed_insert(serialized_origin);
2761    }
2762    // Step 4. Otherwise, if request’s method is neither `GET` nor `HEAD`, then:
2763    else if !matches!(request.method, Method::GET | Method::HEAD) {
2764        // Step 4.1 If request’s mode is not "cors", then switch on request’s referrer policy:
2765        if request.mode != RequestMode::CorsMode {
2766            match request.referrer_policy {
2767                ReferrerPolicy::NoReferrer => {
2768                    // Set serializedOrigin to `null`.
2769                    serialized_origin = headers::Origin::NULL;
2770                },
2771                ReferrerPolicy::NoReferrerWhenDowngrade |
2772                ReferrerPolicy::StrictOrigin |
2773                ReferrerPolicy::StrictOriginWhenCrossOrigin => {
2774                    // If request’s origin is a tuple origin, its scheme is "https", and
2775                    // request’s current URL’s scheme is not "https", then set serializedOrigin to `null`.
2776                    if let ImmutableOrigin::Tuple(scheme, _, _) = &request_origin &&
2777                        scheme == "https" &&
2778                        request.current_url().scheme() != "https"
2779                    {
2780                        serialized_origin = headers::Origin::NULL;
2781                    }
2782                },
2783                ReferrerPolicy::SameOrigin => {
2784                    // If request’s origin is not same origin with request’s current URL’s origin,
2785                    // then set serializedOrigin to `null`.
2786                    if *request_origin != request.current_url().origin() {
2787                        serialized_origin = headers::Origin::NULL;
2788                    }
2789                },
2790                _ => {
2791                    // Otherwise, do nothing.
2792                },
2793            };
2794        }
2795
2796        // Step 4.2. Append (`Origin`, serializedOrigin) to request’s header list.
2797        request.headers.typed_insert(serialized_origin);
2798    }
2799}
2800
2801/// <https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-append-the-fetch-metadata-headers-for-a-request>
2802fn append_the_fetch_metadata_headers(r: &mut Request) {
2803    // Step 1. If r’s url is not an potentially trustworthy URL, return.
2804    if !r.url().is_potentially_trustworthy() {
2805        return;
2806    }
2807
2808    // Step 2. Set the Sec-Fetch-Dest header for r.
2809    set_the_sec_fetch_dest_header(r);
2810
2811    // Step 3. Set the Sec-Fetch-Mode header for r.
2812    set_the_sec_fetch_mode_header(r);
2813
2814    // Step 4. Set the Sec-Fetch-Site header for r.
2815    set_the_sec_fetch_site_header(r);
2816
2817    // Step 5. Set the Sec-Fetch-User header for r.
2818    set_the_sec_fetch_user_header(r);
2819}
2820
2821/// Steps 8.16 to 8.18 in [HTTP network or cache fetch](https://fetch.spec.whatwg.org/#concept-http-network-or-cache-fetch)
2822fn append_cache_data_to_headers(http_request: &mut Request) {
2823    match http_request.cache_mode {
2824        // Step 8.16: If httpRequest’s cache mode is "default" and httpRequest’s header list
2825        // contains `If-Modified-Since`, `If-None-Match`, `If-Unmodified-Since`, `If-Match`, or
2826        // `If-Range`, then set httpRequest’s cache mode to "no-store".
2827        CacheMode::Default if is_no_store_cache(&http_request.headers) => {
2828            http_request.cache_mode = CacheMode::NoStore;
2829        },
2830
2831        // Note that the following steps (8.17 and 8.18) are being considered for removal:
2832        // https://github.com/whatwg/fetch/issues/722#issuecomment-1420264615
2833
2834        // Step 8.17: If httpRequest’s cache mode is "no-cache", httpRequest’s prevent no-cache
2835        // cache-control header modification flag is unset, and httpRequest’s header list does not
2836        // contain `Cache-Control`, then append (`Cache-Control`, `max-age=0`) to httpRequest’s
2837        // header list.
2838        // TODO: Implement request's prevent no-cache cache-control header modification flag
2839        // https://fetch.spec.whatwg.org/#no-cache-prevent-cache-control
2840        CacheMode::NoCache if !http_request.headers.contains_key(header::CACHE_CONTROL) => {
2841            http_request
2842                .headers
2843                .typed_insert(CacheControl::new().with_max_age(Duration::from_secs(0)));
2844        },
2845
2846        // Step 8.18: If httpRequest’s cache mode is "no-store" or "reload", then:
2847        CacheMode::Reload | CacheMode::NoStore => {
2848            // Step 8.18.1: If httpRequest’s header list does not contain `Pragma`, then append
2849            // (`Pragma`, `no-cache`) to httpRequest’s header list.
2850            if !http_request.headers.contains_key(header::PRAGMA) {
2851                http_request.headers.typed_insert(Pragma::no_cache());
2852            }
2853
2854            // Step 8.18.2: If httpRequest’s header list does not contain `Cache-Control`, then
2855            // append (`Cache-Control`, `no-cache`) to httpRequest’s header list.
2856            if !http_request.headers.contains_key(header::CACHE_CONTROL) {
2857                http_request
2858                    .headers
2859                    .typed_insert(CacheControl::new().with_no_cache());
2860            }
2861        },
2862
2863        _ => {},
2864    }
2865}
2866
2867/// <https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-dest>
2868fn set_the_sec_fetch_dest_header(r: &mut Request) {
2869    // Step 1. Assert: r’s url is a potentially trustworthy URL.
2870    debug_assert!(r.url().is_potentially_trustworthy());
2871
2872    // Step 2. Let header be a Structured Header whose value is a token.
2873    // Step 3. If r’s destination is the empty string, set header’s value to the string "empty".
2874    // Otherwise, set header’s value to r’s destination.
2875    let header = r.destination;
2876
2877    // Step 4. Set a structured field value `Sec-Fetch-Dest`/header in r’s header list.
2878    r.headers.typed_insert(SecFetchDest(header));
2879}
2880
2881/// <https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-mode>
2882fn set_the_sec_fetch_mode_header(r: &mut Request) {
2883    // Step 1. Assert: r’s url is a potentially trustworthy URL.
2884    debug_assert!(r.url().is_potentially_trustworthy());
2885
2886    // Step 2. Let header be a Structured Header whose value is a token.
2887    // Step 3. Set header’s value to r’s mode.
2888    let header = &r.mode;
2889
2890    // Step 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list.
2891    r.headers.typed_insert(SecFetchMode::from(header));
2892}
2893
2894/// <https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-site>
2895fn set_the_sec_fetch_site_header(r: &mut Request) {
2896    // The webappsec spec seems to have a similar issue as
2897    // https://github.com/whatwg/fetch/issues/1773
2898    let Origin::Origin(request_origin) = &r.origin else {
2899        panic!("request origin cannot be \"client\" at this point")
2900    };
2901
2902    // Step 1. Assert: r’s url is a potentially trustworthy URL.
2903    debug_assert!(r.url().is_potentially_trustworthy());
2904
2905    // Step 2. Let header be a Structured Header whose value is a token.
2906    // Step 3. Set header’s value to same-origin.
2907    let mut header = SecFetchSite::SameOrigin;
2908
2909    // TODO: Step 3. If r is a navigation request that was explicitly caused by a
2910    // user’s interaction with the user agent, then set header’s value to none.
2911
2912    // Step 5. If header’s value is not none, then for each url in r’s url list:
2913    if header != SecFetchSite::None {
2914        for url in &r.url_list {
2915            // Step 5.1 If url is same origin with r’s origin, continue.
2916            if url.origin() == *request_origin {
2917                continue;
2918            }
2919
2920            // Step 5.2 Set header’s value to cross-site.
2921            header = SecFetchSite::CrossSite;
2922
2923            // Step 5.3 If r’s origin is not same site with url’s origin, then break.
2924            if !is_same_site(request_origin, &url.origin()) {
2925                break;
2926            }
2927
2928            // Step 5.4 Set header’s value to same-site.
2929            header = SecFetchSite::SameSite;
2930        }
2931    }
2932
2933    // Step 6. Set a structured field value `Sec-Fetch-Site`/header in r’s header list.
2934    r.headers.typed_insert(header);
2935}
2936
2937/// <https://w3c.github.io/webappsec-fetch-metadata/#abstract-opdef-set-user>
2938fn set_the_sec_fetch_user_header(r: &mut Request) {
2939    // Step 1. Assert: r’s url is a potentially trustworthy URL.
2940    debug_assert!(r.url().is_potentially_trustworthy());
2941
2942    // Step 2. If r is not a navigation request, or if r’s user-activation is false, return.
2943    // TODO user activation
2944    if !r.is_navigation_request() {
2945        return;
2946    }
2947
2948    // Step 3. Let header be a Structured Header whose value is a token.
2949    // Step 4. Set header’s value to true.
2950    let header = SecFetchUser;
2951
2952    // Step 5. Set a structured field value `Sec-Fetch-User`/header in r’s header list.
2953    r.headers.typed_insert(header);
2954}
2955
2956/// <https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect>
2957fn set_requests_referrer_policy_on_redirect(request: &mut Request, response: &Response) {
2958    // Step 1: Let policy be the result of executing § 8.1 Parse a referrer policy from a
2959    // Referrer-Policy header on actualResponse.
2960    let referrer_policy: ReferrerPolicy = response
2961        .headers
2962        .typed_get::<headers::ReferrerPolicy>()
2963        .into();
2964
2965    // Step 2: If policy is not the empty string, then set request’s referrer policy to policy.
2966    if referrer_policy != ReferrerPolicy::EmptyString {
2967        request.referrer_policy = referrer_policy;
2968    }
2969}