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