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