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