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