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