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