net/
http_loader.rs

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