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