net/
http_loader.rs

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