Skip to main content

net/
http_loader.rs

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