net/fetch/
methods.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::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::{io, mem, str};
8
9use base64::Engine as _;
10use base64::engine::general_purpose;
11use content_security_policy as csp;
12use crossbeam_channel::Sender;
13use devtools_traits::DevtoolsControlMsg;
14use embedder_traits::resources::{self, Resource};
15use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt};
16use http::header::{self, HeaderMap, HeaderName, RANGE};
17use http::{HeaderValue, Method, StatusCode};
18use ipc_channel::ipc::{self, IpcSender};
19use log::{debug, trace, warn};
20use malloc_size_of_derive::MallocSizeOf;
21use mime::{self, Mime};
22use net_traits::fetch::headers::{determine_nosniff, extract_mime_type_as_mime};
23use net_traits::filemanager_thread::{FileTokenCheck, RelativePos};
24use net_traits::http_status::HttpStatus;
25use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
26use net_traits::request::{
27    BodyChunkRequest, BodyChunkResponse, CredentialsMode, Destination, Initiator,
28    InsecureRequestsPolicy, Origin, ParserMetadata, RedirectMode, Referrer, Request, RequestBody,
29    RequestId, RequestMode, ResponseTainting, is_cors_safelisted_method,
30    is_cors_safelisted_request_header,
31};
32use net_traits::response::{Response, ResponseBody, ResponseType, TerminationReason};
33use net_traits::{
34    FetchTaskTarget, NetworkError, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming,
35    ResourceTimeValue, ResourceTimingType, WebSocketDomAction, WebSocketNetworkEvent,
36    set_default_accept_language,
37};
38use parking_lot::Mutex;
39use rustc_hash::FxHashMap;
40use rustls_pki_types::CertificateDer;
41use serde::{Deserialize, Serialize};
42use servo_arc::Arc as ServoArc;
43use servo_base::generic_channel::CallbackSetter;
44use servo_base::id::PipelineId;
45use servo_url::{Host, ImmutableOrigin, ServoUrl};
46use tokio::sync::Mutex as TokioMutex;
47use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender};
48
49use crate::connector::CACertificates;
50use crate::fetch::cors_cache::CorsCache;
51use crate::fetch::fetch_params::{
52    ConsumePreloadedResources, FetchParams, SharedPreloadedResources,
53};
54use crate::filemanager_thread::FileManager;
55use crate::http_loader::{
56    HttpState, determine_requests_referrer, http_fetch, send_early_httprequest_to_devtools,
57    send_response_to_devtools, send_security_info_to_devtools, set_default_accept,
58};
59use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy};
60use crate::request_interceptor::RequestInterceptor;
61use crate::subresource_integrity::is_response_integrity_valid;
62
63pub type Target<'a> = &'a mut (dyn FetchTaskTarget + Send);
64
65#[derive(Clone, Deserialize, Serialize)]
66pub enum Data {
67    Payload(Vec<u8>),
68    Done,
69    Cancelled,
70    Error(NetworkError),
71}
72
73pub struct WebSocketChannel {
74    pub sender: IpcSender<WebSocketNetworkEvent>,
75    pub receiver: Option<CallbackSetter<WebSocketDomAction>>,
76}
77
78impl WebSocketChannel {
79    pub fn new(
80        sender: IpcSender<WebSocketNetworkEvent>,
81        receiver: Option<CallbackSetter<WebSocketDomAction>>,
82    ) -> Self {
83        Self { sender, receiver }
84    }
85}
86
87/// Used to keep track of keep-alive requests
88#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
89pub struct InFlightKeepAliveRecord {
90    pub(crate) request_id: RequestId,
91    /// Used to keep track of size of keep-alive requests.
92    pub(crate) keep_alive_body_length: u64,
93}
94
95pub type SharedInflightKeepAliveRecords =
96    Arc<Mutex<FxHashMap<PipelineId, Vec<InFlightKeepAliveRecord>>>>;
97
98#[derive(Clone)]
99pub struct FetchContext {
100    pub state: Arc<HttpState>,
101    pub user_agent: String,
102    pub devtools_chan: Option<Sender<DevtoolsControlMsg>>,
103    pub filemanager: FileManager,
104    pub file_token: FileTokenCheck,
105    pub request_interceptor: Arc<TokioMutex<RequestInterceptor>>,
106    pub cancellation_listener: Arc<CancellationListener>,
107    pub timing: ServoArc<Mutex<ResourceFetchTiming>>,
108    pub protocols: Arc<ProtocolRegistry>,
109    pub websocket_chan: Option<Arc<Mutex<WebSocketChannel>>>,
110    pub ca_certificates: CACertificates<'static>,
111    pub ignore_certificate_errors: bool,
112    pub preloaded_resources: SharedPreloadedResources,
113    pub in_flight_keep_alive_records: SharedInflightKeepAliveRecords,
114}
115
116#[derive(Default)]
117pub struct CancellationListener {
118    cancelled: AtomicBool,
119}
120
121impl CancellationListener {
122    pub(crate) fn cancelled(&self) -> bool {
123        self.cancelled.load(Ordering::Relaxed)
124    }
125
126    pub(crate) fn cancel(&self) {
127        self.cancelled.store(true, Ordering::Relaxed)
128    }
129}
130
131/// Closes the current process request body sender state when the net side fetch invocation ends.
132/// Redirect replay for navigation requests happens in a later fetch invocation with a newly
133/// deserialized "RequestBody", so each invocation owns closing only its local copy.
134pub(crate) struct AutoRequestBodyStreamCloser {
135    body: Option<RequestBody>,
136}
137
138impl AutoRequestBodyStreamCloser {
139    pub(crate) fn new(body: Option<&RequestBody>) -> Self {
140        Self {
141            body: body.cloned(),
142        }
143    }
144
145    pub(crate) fn disarm(&mut self) {
146        self.body = None;
147    }
148}
149
150impl Drop for AutoRequestBodyStreamCloser {
151    fn drop(&mut self) {
152        if let Some(body) = self.body.take() {
153            body.close_stream();
154        }
155    }
156}
157
158/// A manual navigation redirect keeps the same request body alive for a later net side redirect
159/// replay invocation. That later invocation becomes the next lifecycle owner and must close its
160/// local shared sender state once it reaches a terminal response.
161pub(crate) fn transfers_request_body_stream_to_later_manual_redirect(
162    request: &Request,
163    response: &Response,
164) -> bool {
165    request.mode == RequestMode::Navigate &&
166        request.redirect_mode == RedirectMode::Manual &&
167        request.body.is_some() &&
168        !response.is_network_error() &&
169        response
170            .actual_response()
171            .status
172            .try_code()
173            .is_some_and(|status| status.is_redirection())
174}
175
176pub type DoneChannel = Option<(TokioSender<Data>, TokioReceiver<Data>)>;
177
178/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
179pub async fn fetch(request: Request, target: Target<'_>, context: &FetchContext) -> Response {
180    // Steps 7,4 of https://w3c.github.io/resource-timing/#processing-model
181    // rev order okay since spec says they're equal - https://w3c.github.io/resource-timing/#dfn-starttime
182    {
183        let mut timing_guard = context.timing.lock();
184        timing_guard.set_attribute(ResourceAttribute::FetchStart);
185        timing_guard.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart));
186    }
187    fetch_with_cors_cache(request, &mut CorsCache::default(), target, context).await
188}
189
190/// Continuation of fetch from step 8.
191///
192/// <https://fetch.spec.whatwg.org#concept-fetch>
193pub async fn fetch_with_cors_cache(
194    request: Request,
195    cache: &mut CorsCache,
196    target: Target<'_>,
197    context: &FetchContext,
198) -> Response {
199    // Step 8. Let fetchParams be a new fetch params whose request is request
200    let mut fetch_params = FetchParams::new(request);
201    // Each net side fetch invocation owns closing its local deserialized request-body sender state
202    // once this function returns, even if navigation redirect replay later starts a new fetch with
203    // a fresh "RequestBody" copy.
204    let mut request_body_stream_closer =
205        AutoRequestBodyStreamCloser::new(fetch_params.request.body.as_ref());
206    let request = &mut fetch_params.request;
207
208    // Step 4. Populate request from client given request.
209    request.populate_request_from_client();
210
211    // Step 5. If request’s client is non-null, then:
212    // TODO
213    // Step 5.1. Set taskDestination to request’s client’s global object.
214    // TODO
215    // Step 5.2. Set crossOriginIsolatedCapability to request’s client’s cross-origin isolated capability.
216    // TODO
217
218    // Step 10. If all of the following conditions are true:
219    if
220    // - request’s URL’s scheme is an HTTP(S) scheme
221    matches!(request.current_url().scheme(), "http" | "https")
222        // - request’s mode is "same-origin", "cors", or "no-cors"
223        && matches!(request.mode, RequestMode::SameOrigin | RequestMode::CorsMode | RequestMode::NoCors)
224        // - request’s method is `GET`
225        && matches!(request.method, Method::GET)
226        // - request’s unsafe-request flag is not set or request’s header list is empty
227        && (!request.unsafe_request || request.headers.is_empty())
228    {
229        // - request’s client is not null, and request’s client’s global object is a Window object
230        if let Some(client) = request.client.as_ref() {
231            // Step 10.1. Assert: request’s origin is same origin with request’s client’s origin.
232            assert!(request.origin == client.origin);
233            // Step 10.2. Let onPreloadedResponseAvailable be an algorithm that runs the
234            // following step given a response response: set fetchParams’s preloaded response candidate to response.
235            // Step 10.3. Let foundPreloadedResource be the result of invoking consume a preloaded resource
236            // for request’s client, given request’s URL, request’s destination, request’s mode,
237            // request’s credentials mode, request’s integrity metadata, and onPreloadedResponseAvailable.
238            // Step 10.4. If foundPreloadedResource is true and fetchParams’s preloaded response candidate is null,
239            // then set fetchParams’s preloaded response candidate to "pending".
240            if let Some(candidate) =
241                client.consume_preloaded_resource(request, context.preloaded_resources.clone())
242            {
243                fetch_params.preload_response_candidate = candidate;
244            }
245        }
246    }
247
248    // Step 11. If request’s header list does not contain `Accept`, then:
249    set_default_accept(request);
250
251    // Step 12. If request’s header list does not contain `Accept-Language`, then user agents should
252    // append (`Accept-Language, an appropriate header value) to request’s header list.
253    set_default_accept_language(&mut request.headers);
254
255    // Step 15. If request’s internal priority is null, then use request’s priority, initiator,
256    // destination, and render-blocking in an implementation-defined manner to set request’s
257    // internal priority to an implementation-defined object.
258    // TODO: figure out what a Priority object is.
259
260    // Step 15. If request is a subresource request:
261    //
262    // We only check for keep-alive requests here, since that's currently the only usage
263    let should_track_in_flight_record = request.keep_alive && request.is_subresource_request();
264    let pipeline_id = request.pipeline_id;
265
266    if should_track_in_flight_record {
267        // Step 15.1. Let record be a new fetch record whose request is request
268        // and controller is fetchParams’s controller.
269        let record = InFlightKeepAliveRecord {
270            request_id: request.id,
271            keep_alive_body_length: request.keep_alive_body_length(),
272        };
273        // Step 15.2. Append record to request’s client’s fetch group’s fetch records.
274        let mut in_flight_records = context.in_flight_keep_alive_records.lock();
275        in_flight_records
276            .entry(pipeline_id.expect("Must always set a pipeline ID for keep-alive requests"))
277            .or_default()
278            .push(record);
279    };
280    let request_id = request.id;
281
282    // Step 17: Run main fetch given fetchParams.
283    let response = main_fetch(&mut fetch_params, cache, false, target, &mut None, context).await;
284
285    if transfers_request_body_stream_to_later_manual_redirect(&fetch_params.request, &response) {
286        request_body_stream_closer.disarm();
287    }
288
289    // Mimics <https://fetch.spec.whatwg.org/#done-flag>
290    if should_track_in_flight_record {
291        context
292            .in_flight_keep_alive_records
293            .lock()
294            .get_mut(&pipeline_id.expect("Must always set a pipeline ID for keep-alive requests"))
295            .expect("Must always have initialized tracked requests before starting fetch")
296            .retain(|record| record.request_id != request_id);
297    }
298
299    // Step 18: Return fetchParams’s controller.
300    // TODO: We don't implement fetchParams as defined in the spec
301    response
302}
303
304pub(crate) fn convert_request_to_csp_request(request: &Request) -> Option<csp::Request> {
305    let origin = match &request.origin {
306        Origin::Client => return None,
307        Origin::Origin(origin) => origin,
308    };
309
310    let csp_request = csp::Request {
311        url: request.url().into_url(),
312        current_url: request.current_url().into_url(),
313        origin: origin.clone().into_url_origin(),
314        redirect_count: request.redirect_count,
315        destination: request.destination,
316        initiator: match request.initiator {
317            Initiator::Download => csp::Initiator::Download,
318            Initiator::ImageSet => csp::Initiator::ImageSet,
319            Initiator::Manifest => csp::Initiator::Manifest,
320            Initiator::Prefetch => csp::Initiator::Prefetch,
321            _ => csp::Initiator::None,
322        },
323        nonce: request.cryptographic_nonce_metadata.clone(),
324        integrity_metadata: request.integrity_metadata.clone(),
325        parser_metadata: match request.parser_metadata {
326            ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted,
327            ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted,
328            ParserMetadata::Default => csp::ParserMetadata::None,
329        },
330    };
331    Some(csp_request)
332}
333
334/// <https://www.w3.org/TR/CSP/#should-block-request>
335pub fn should_request_be_blocked_by_csp(
336    csp_request: &csp::Request,
337    policy_container: &PolicyContainer,
338) -> (csp::CheckResult, Vec<csp::Violation>) {
339    policy_container
340        .csp_list
341        .as_ref()
342        .map(|c| c.should_request_be_blocked(csp_request))
343        .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
344}
345
346/// <https://www.w3.org/TR/CSP/#report-for-request>
347pub fn report_violations_for_request_by_csp(
348    csp_request: &csp::Request,
349    policy_container: &PolicyContainer,
350) -> Vec<csp::Violation> {
351    policy_container
352        .csp_list
353        .as_ref()
354        .map(|c| c.report_violations_for_request(csp_request))
355        .unwrap_or_default()
356}
357
358fn should_response_be_blocked_by_csp(
359    csp_request: &csp::Request,
360    response: &Response,
361    policy_container: &PolicyContainer,
362) -> (csp::CheckResult, Vec<csp::Violation>) {
363    if response.is_network_error() {
364        return (csp::CheckResult::Allowed, Vec::new());
365    }
366    let csp_response = csp::Response {
367        url: response
368            .actual_response()
369            .url()
370            .cloned()
371            // NOTE(pylbrecht): for WebSocket connections, the URL scheme is converted to http(s)
372            // to integrate with fetch(). We need to convert it back to ws(s) to get valid CSP
373            // checks.
374            // https://github.com/w3c/webappsec-csp/issues/532
375            .map(|mut url| {
376                match csp_request.url.scheme() {
377                    "ws" | "wss" => {
378                        url.as_mut_url()
379                            .set_scheme(csp_request.url.scheme())
380                            .expect("failed to set URL scheme");
381                    },
382                    _ => {},
383                };
384                url
385            })
386            .expect("response must have a url")
387            .into_url(),
388        redirect_count: csp_request.redirect_count,
389    };
390    policy_container
391        .csp_list
392        .as_ref()
393        .map(|c| c.should_response_to_request_be_blocked(csp_request, &csp_response))
394        .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
395}
396
397/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
398pub async fn main_fetch(
399    fetch_params: &mut FetchParams,
400    cache: &mut CorsCache,
401    recursive_flag: bool,
402    target: Target<'_>,
403    done_chan: &mut DoneChannel,
404    context: &FetchContext,
405) -> Response {
406    // Step 1: Let request be fetchParam's request.
407    let request = &mut fetch_params.request;
408    send_early_httprequest_to_devtools(request, context);
409    // Step 2: Let response be null.
410    let mut response = None;
411
412    // Servo internal: return a crash error when a crash error page is needed
413    if let Some(ref details) = request.crash {
414        response = Some(Response::network_error(NetworkError::Crash(
415            details.clone(),
416        )));
417    }
418
419    // Step 3: If request’s local-URLs-only flag is set and request’s
420    // current URL is not local, then set response to a network error.
421    if request.local_urls_only &&
422        !matches!(
423            request.current_url().scheme(),
424            "about" | "blob" | "data" | "filesystem"
425        )
426    {
427        response = Some(Response::network_error(NetworkError::UnsupportedScheme));
428    }
429
430    // The request should have a valid policy_container associated with it.
431    let policy_container = match &request.policy_container {
432        RequestPolicyContainer::Client => unreachable!(),
433        RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
434    };
435    let csp_request = convert_request_to_csp_request(request);
436    if let Some(csp_request) = csp_request.as_ref() {
437        // Step 2.2.
438        let violations = report_violations_for_request_by_csp(csp_request, &policy_container);
439
440        if !violations.is_empty() {
441            target.process_csp_violations(request, violations);
442        }
443    };
444
445    // Step 3.
446    // TODO: handle request abort.
447
448    // Step 4. Upgrade request to a potentially trustworthy URL, if appropriate.
449    if should_upgrade_request_to_potentially_trustworthy(request, context) ||
450        should_upgrade_mixed_content_request(request, &context.protocols)
451    {
452        trace!(
453            "upgrading {} targeting {:?}",
454            request.current_url(),
455            request.destination
456        );
457        if let Some(new_scheme) = match request.current_url().scheme() {
458            "http" => Some("https"),
459            "ws" => Some("wss"),
460            _ => None,
461        } {
462            request
463                .current_url_mut()
464                .as_mut_url()
465                .set_scheme(new_scheme)
466                .unwrap();
467        }
468    } else {
469        trace!(
470            "not upgrading {} targeting {:?} with {:?}",
471            request.current_url(),
472            request.destination,
473            request.insecure_requests_policy
474        );
475    }
476    if let Some(csp_request) = csp_request.as_ref() {
477        // Step 7. If should request be blocked due to a bad port, should fetching request be blocked
478        // as mixed content, or should request be blocked by Content Security Policy returns blocked,
479        // then set response to a network error.
480        let (check_result, violations) =
481            should_request_be_blocked_by_csp(csp_request, &policy_container);
482
483        if !violations.is_empty() {
484            target.process_csp_violations(request, violations);
485        }
486
487        if check_result == csp::CheckResult::Blocked {
488            warn!("Request blocked by CSP");
489            response = Some(Response::network_error(NetworkError::ContentSecurityPolicy))
490        }
491    };
492    if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) {
493        response = Some(Response::network_error(NetworkError::InvalidPort));
494    }
495    if should_request_be_blocked_as_mixed_content(request, &context.protocols) {
496        response = Some(Response::network_error(NetworkError::MixedContent));
497    }
498
499    // Step 8: If request’s referrer policy is the empty string, then set request’s referrer policy
500    // to request’s policy container’s referrer policy.
501    if request.referrer_policy == ReferrerPolicy::EmptyString {
502        request.referrer_policy = policy_container.get_referrer_policy();
503    }
504
505    let referrer_url = match mem::replace(&mut request.referrer, Referrer::NoReferrer) {
506        Referrer::NoReferrer => None,
507        Referrer::ReferrerUrl(referrer_source) | Referrer::Client(referrer_source) => {
508            request.headers.remove(header::REFERER);
509            determine_requests_referrer(
510                request.referrer_policy,
511                referrer_source,
512                request.current_url(),
513            )
514        },
515    };
516    request.referrer = referrer_url.map_or(Referrer::NoReferrer, Referrer::ReferrerUrl);
517
518    // Step 9.
519    // TODO: handle FTP URLs.
520
521    // Step 10.
522    context
523        .state
524        .hsts_list
525        .read()
526        .apply_hsts_rules(request.current_url_mut());
527
528    // Step 11.
529    // Not applicable: see fetch_async.
530
531    let current_url = request.current_url();
532    let current_scheme = current_url.scheme();
533
534    // Intercept the request and maybe override the response.
535    context
536        .request_interceptor
537        .lock()
538        .await
539        .intercept_request(request, &mut response, context)
540        .await;
541
542    let mut response = match response {
543        Some(response) => response,
544        None => {
545            // Step 12. If response is null, then set response to the result
546            // of running the steps corresponding to the first matching statement:
547            let same_origin = if let Origin::Origin(ref origin) = request.origin {
548                *origin == request.current_url_with_blob_claim().origin()
549            } else {
550                false
551            };
552
553            // fetchParams’s preloaded response candidate is non-null
554            if let Some((response, preload_id)) =
555                fetch_params.preload_response_candidate.response().await
556            {
557                response.get_resource_timing().lock().preloaded = true;
558                context
559                    .preloaded_resources
560                    .lock()
561                    .unwrap()
562                    .remove(&preload_id);
563                response
564            }
565            // request's current URL's origin is same origin with request's origin, and request's
566            // response tainting is "basic"
567            else if (same_origin && request.response_tainting == ResponseTainting::Basic) ||
568                // request's current URL's scheme is "data"
569                current_scheme == "data" ||
570                // Note: Although it is not part of the specification, we make an exception here
571                // for custom protocols that are explicitly marked as active for fetch.
572                context.protocols.is_fetchable(current_scheme) ||
573                // request's mode is "navigate" or "websocket"
574                matches!(
575                    request.mode,
576                    RequestMode::Navigate | RequestMode::WebSocket { .. }
577                )
578            {
579                // Substep 1. Set request's response tainting to "basic".
580                request.response_tainting = ResponseTainting::Basic;
581
582                // Substep 2. Return the result of running scheme fetch given fetchParams.
583                scheme_fetch(fetch_params, cache, target, done_chan, context).await
584            } else if request.mode == RequestMode::SameOrigin {
585                Response::network_error(NetworkError::CrossOriginResponse)
586            } else if request.mode == RequestMode::NoCors {
587                // Substep 1. If request's redirect mode is not "follow", then return a network error.
588                if request.redirect_mode != RedirectMode::Follow {
589                    Response::network_error(NetworkError::RedirectError)
590                } else {
591                    // Substep 2. Set request's response tainting to "opaque".
592                    request.response_tainting = ResponseTainting::Opaque;
593
594                    // Substep 3. Return the result of running scheme fetch given fetchParams.
595                    scheme_fetch(fetch_params, cache, target, done_chan, context).await
596                }
597            } else if !matches!(current_scheme, "http" | "https") {
598                Response::network_error(NetworkError::UnsupportedScheme)
599            } else if request.use_cors_preflight ||
600                (request.unsafe_request &&
601                    (!is_cors_safelisted_method(&request.method) ||
602                        request.headers.iter().any(|(name, value)| {
603                            !is_cors_safelisted_request_header(&name, &value)
604                        })))
605            {
606                // Substep 1.
607                request.response_tainting = ResponseTainting::CorsTainting;
608                // Substep 2.
609                let response = http_fetch(
610                    fetch_params,
611                    cache,
612                    true,
613                    true,
614                    false,
615                    target,
616                    done_chan,
617                    context,
618                )
619                .await;
620                // Substep 3.
621                if response.is_network_error() {
622                    // TODO clear cache entries using request
623                }
624                // Substep 4.
625                response
626            } else {
627                // Substep 1.
628                request.response_tainting = ResponseTainting::CorsTainting;
629                // Substep 2.
630                http_fetch(
631                    fetch_params,
632                    cache,
633                    true,
634                    false,
635                    false,
636                    target,
637                    done_chan,
638                    context,
639                )
640                .await
641            }
642        },
643    };
644
645    // Step 13. If recursive is true, then return response.
646    if recursive_flag {
647        return response;
648    }
649
650    // reborrow request to avoid double mutable borrow
651    let request = &mut fetch_params.request;
652
653    // Step 14. If response is not a network error and response is not a filtered response, then:
654    let mut response = if !response.is_network_error() && response.internal_response.is_none() {
655        // Substep 1.
656        if request.response_tainting == ResponseTainting::CorsTainting {
657            // Subsubstep 1.
658            let header_names: Option<Vec<HeaderName>> = response
659                .headers
660                .typed_get::<AccessControlExposeHeaders>()
661                .map(|v| v.iter().collect());
662            match header_names {
663                // Subsubstep 2.
664                Some(ref list)
665                    if request.credentials_mode != CredentialsMode::Include &&
666                        list.iter().any(|header| header == "*") =>
667                {
668                    response.cors_exposed_header_name_list = response
669                        .headers
670                        .iter()
671                        .map(|(name, _)| name.as_str().to_owned())
672                        .collect();
673                },
674                // Subsubstep 3.
675                Some(list) => {
676                    response.cors_exposed_header_name_list =
677                        list.iter().map(|h| h.as_str().to_owned()).collect();
678                },
679                _ => (),
680            }
681        }
682
683        // Substep 2.
684        let response_type = match request.response_tainting {
685            ResponseTainting::Basic => ResponseType::Basic,
686            ResponseTainting::CorsTainting => ResponseType::Cors,
687            ResponseTainting::Opaque => ResponseType::Opaque,
688        };
689        response.to_filtered(response_type)
690    } else {
691        response
692    };
693
694    let internal_error = {
695        // Tests for steps 17 and 18, before step 15 for borrowing concerns.
696        let response_is_network_error = response.is_network_error();
697        let should_replace_with_nosniff_error = !response_is_network_error &&
698            should_be_blocked_due_to_nosniff(request.destination, &response.headers);
699        let should_replace_with_mime_type_error = !response_is_network_error &&
700            should_be_blocked_due_to_mime_type(request.destination, &response.headers);
701        let should_replace_with_mixed_content = !response_is_network_error &&
702            should_response_be_blocked_as_mixed_content(request, &response, &context.protocols);
703        let should_replace_with_csp_error = csp_request.is_some_and(|csp_request| {
704            let (check_result, violations) =
705                should_response_be_blocked_by_csp(&csp_request, &response, &policy_container);
706            if !violations.is_empty() {
707                target.process_csp_violations(request, violations);
708            }
709            check_result == csp::CheckResult::Blocked
710        });
711
712        // Step 15.
713        let mut network_error_response = response
714            .get_network_error()
715            .cloned()
716            .map(Response::network_error);
717
718        // Step 15. Let internalResponse be response, if response is a network error;
719        // otherwise response’s internal response.
720        let response_type = response.response_type.clone(); // Needed later after the mutable borrow
721        let internal_response = if let Some(error_response) = network_error_response.as_mut() {
722            error_response
723        } else {
724            response.actual_response_mut()
725        };
726
727        // Step 16. If internalResponse’s URL list is empty, then set it to a clone of request’s URL list.
728        if internal_response.url_list.is_empty() {
729            internal_response.url_list = request
730                .url_list
731                .iter()
732                .map(|locked_url| locked_url.url())
733                .collect();
734        }
735
736        // Step 17. Set internalResponse’s redirect taint to request’s redirect-taint.
737        internal_response.redirect_taint = request.redirect_taint_for_request();
738
739        // Step 19. If response is not a network error and any of the following returns blocked
740        // * should internalResponse to request be blocked as mixed content
741        // * should internalResponse to request be blocked by Content Security Policy
742        // * should internalResponse to request be blocked due to its MIME type
743        // * should internalResponse to request be blocked due to nosniff
744        let mut blocked_error_response;
745
746        let internal_response = if should_replace_with_nosniff_error {
747            // Defer rebinding result
748            blocked_error_response = Response::network_error(NetworkError::Nosniff);
749            &blocked_error_response
750        } else if should_replace_with_mime_type_error {
751            // Defer rebinding result
752            blocked_error_response =
753                Response::network_error(NetworkError::MimeType("Blocked by MIME type".into()));
754            &blocked_error_response
755        } else if should_replace_with_mixed_content {
756            blocked_error_response = Response::network_error(NetworkError::MixedContent);
757            &blocked_error_response
758        } else if should_replace_with_csp_error {
759            blocked_error_response = Response::network_error(NetworkError::ContentSecurityPolicy);
760            &blocked_error_response
761        } else {
762            internal_response
763        };
764
765        // Step 20. If response’s type is "opaque", internalResponse’s status is 206, internalResponse’s
766        // range-requested flag is set, and request’s header list does not contain `Range`, then set
767        // response and internalResponse to a network error.
768        // Also checking if internal response is a network error to prevent crash from attemtping to
769        // read status of a network error if we blocked the request above.
770        let internal_response = if !internal_response.is_network_error() &&
771            response_type == ResponseType::Opaque &&
772            internal_response.status.code() == StatusCode::PARTIAL_CONTENT &&
773            internal_response.range_requested &&
774            !request.headers.contains_key(RANGE)
775        {
776            // Defer rebinding result
777            blocked_error_response =
778                Response::network_error(NetworkError::PartialResponseToNonRangeRequestError);
779            &blocked_error_response
780        } else {
781            internal_response
782        };
783
784        // Step 21. If response is not a network error and either request’s method is `HEAD` or `CONNECT`,
785        // or internalResponse’s status is a null body status, set internalResponse’s body to null and
786        // disregard any enqueuing toward it (if any).
787        // NOTE: We check `internal_response` since we did not mutate `response` in the previous steps.
788        let not_network_error = !response_is_network_error && !internal_response.is_network_error();
789        if not_network_error &&
790            (is_null_body_status(&internal_response.status) ||
791                matches!(request.method, Method::HEAD | Method::CONNECT))
792        {
793            // when Fetch is used only asynchronously, we will need to make sure
794            // that nothing tries to write to the body at this point
795            let mut body = internal_response.body.lock();
796            *body = ResponseBody::Empty;
797        }
798
799        internal_response.get_network_error().cloned()
800    };
801
802    // Execute deferred rebinding of response.
803    let mut response = if let Some(error) = internal_error {
804        Response::network_error(error)
805    } else {
806        response
807    };
808
809    // Step 19. If response is not a network error and any of the following returns blocked
810    let mut response_loaded = false;
811    let mut response = if !response.is_network_error() && !request.integrity_metadata.is_empty() {
812        // Step 19.1.
813        wait_for_response(request, &mut response, target, done_chan, context).await;
814        response_loaded = true;
815
816        // Step 19.2.
817        let integrity_metadata = &request.integrity_metadata;
818        if response.termination_reason.is_none() &&
819            !is_response_integrity_valid(integrity_metadata, &response)
820        {
821            Response::network_error(NetworkError::SubresourceIntegrity)
822        } else {
823            response
824        }
825    } else {
826        response
827    };
828
829    // Step 20.
830    if request.synchronous {
831        // process_response is not supposed to be used
832        // by sync fetch, but we overload it here for simplicity
833        target.process_response(request, &response);
834        if !response_loaded {
835            wait_for_response(request, &mut response, target, done_chan, context).await;
836        }
837        // overloaded similarly to process_response
838        target.process_response_eof(request, &response);
839        return response;
840    }
841
842    // Step 21.
843    if request.body.is_some() && matches!(current_scheme, "http" | "https") {
844        // XXXManishearth: We actually should be calling process_request
845        // in http_network_fetch. However, we can't yet follow the request
846        // upload progress, so I'm keeping it here for now and pretending
847        // the body got sent in one chunk
848        target.process_request_body(request);
849    }
850
851    // Step 22.
852    target.process_response(request, &response);
853    // Send Response to Devtools
854    send_response_to_devtools(request, context, &response, None);
855    send_security_info_to_devtools(request, context, &response);
856
857    // Step 23.
858    if !response_loaded {
859        wait_for_response(request, &mut response, target, done_chan, context).await;
860    }
861
862    // Step 24.
863    target.process_response_eof(request, &response);
864    // Send Response to Devtools
865    // This is done after process_response_eof to ensure that the body is fully
866    // processed before sending the response to Devtools.
867    send_response_to_devtools(request, context, &response, None);
868
869    context
870        .state
871        .http_cache
872        .update_awaiting_consumers(request, &response)
873        .await;
874
875    // Steps 25-27.
876    // TODO: remove this line when only asynchronous fetches are used
877    response
878}
879
880async fn wait_for_response(
881    request: &Request,
882    response: &mut Response,
883    target: Target<'_>,
884    done_chan: &mut DoneChannel,
885    context: &FetchContext,
886) {
887    if let Some(ref mut ch) = *done_chan {
888        let mut devtools_body = context.devtools_chan.as_ref().map(|_| Vec::new());
889        loop {
890            match ch.1.recv().await {
891                Some(Data::Payload(vec)) => {
892                    if let Some(body) = devtools_body.as_mut() {
893                        body.extend(&vec);
894                    }
895                    target.process_response_chunk(request, vec);
896                },
897                Some(Data::Error(network_error)) => {
898                    if network_error == NetworkError::DecompressionError {
899                        response.termination_reason = Some(TerminationReason::Fatal);
900                    }
901                    response.set_network_error(network_error);
902
903                    break;
904                },
905                Some(Data::Done) => {
906                    send_response_to_devtools(request, context, response, devtools_body);
907                    break;
908                },
909                Some(Data::Cancelled) => {
910                    response.aborted.store(true, Ordering::Release);
911                    break;
912                },
913                _ => {
914                    panic!("fetch worker should always send Done before terminating");
915                },
916            }
917        }
918    } else {
919        match *response.actual_response().body.lock() {
920            ResponseBody::Done(ref vec) if !vec.is_empty() => {
921                // in case there was no channel to wait for, the body was
922                // obtained synchronously via scheme_fetch for data/file/about/etc
923                // We should still send the body across as a chunk
924                target.process_response_chunk(request, vec.clone());
925                if context.devtools_chan.is_some() {
926                    // Now that we've replayed the entire cached body,
927                    // notify the DevTools server with the full Response.
928                    send_response_to_devtools(request, context, response, Some(vec.clone()));
929                }
930            },
931            ResponseBody::Done(_) | ResponseBody::Empty => {},
932            _ => unreachable!(),
933        }
934    }
935}
936
937/// Range header start and end values.
938pub enum RangeRequestBounds {
939    /// The range bounds are known and set to final values.
940    Final(RelativePos),
941    /// We need extra information to set the range bounds.
942    /// i.e. buffer or file size.
943    Pending(u64),
944}
945
946impl RangeRequestBounds {
947    pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, &'static str> {
948        match self {
949            RangeRequestBounds::Final(pos) => {
950                if let Some(len) = len {
951                    if pos.start <= len as i64 {
952                        return Ok(*pos);
953                    }
954                }
955                Err("Tried to process RangeRequestBounds::Final without len")
956            },
957            RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
958                if let Some(len) = len {
959                    Some((len - u64::min(len, *offset)) as i64)
960                } else {
961                    Some(0)
962                },
963                None,
964            )),
965        }
966    }
967}
968
969fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
970    let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
971    response
972        .headers
973        .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
974    *response.body.lock() = ResponseBody::Done(vec![]);
975    response.status = HttpStatus::default();
976    response
977}
978
979fn create_about_memory(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
980    let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
981    response
982        .headers
983        .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
984    *response.body.lock() = ResponseBody::Done(resources::read_bytes(Resource::AboutMemoryHTML));
985    response.status = HttpStatus::default();
986    response
987}
988
989/// Handle a request from the user interface to ignore validation errors for a certificate.
990fn handle_allowcert_request(request: &mut Request, context: &FetchContext) -> io::Result<()> {
991    let error = |string| Err(io::Error::other(string));
992
993    let body = match request.body.as_mut() {
994        Some(body) => body,
995        None => return error("No body found"),
996    };
997
998    let stream = body.clone_stream();
999    let mut stream = stream.lock();
1000    let (body_chan, body_port) = ipc::channel().unwrap();
1001    let Some(chunk_requester) = stream.as_mut() else {
1002        log::error!(
1003            "Could not connect to the request body stream because it has already been closed."
1004        );
1005        return Err(std::io::Error::other("Could not send BodyChunkRequest"));
1006    };
1007    chunk_requester
1008        .send(BodyChunkRequest::Connect(body_chan))
1009        .map_err(|error| {
1010            log::error!(
1011                "Could not connect to the request body stream because it has already been closed: {error}"
1012            );
1013            std::io::Error::other("Could not connect to request body stream")
1014        })?;
1015    chunk_requester
1016        .send(BodyChunkRequest::Chunk)
1017        .map_err(|error| {
1018            log::error!(
1019                "Could not request the first request body chunk because the body stream has already been closed: {error}"
1020            );
1021            std::io::Error::other("Could not request request body chunk")
1022        })?;
1023    let body_bytes = match body_port.recv().ok() {
1024        Some(BodyChunkResponse::Chunk(bytes)) => bytes,
1025        _ => return error("Certificate not sent in a single chunk"),
1026    };
1027
1028    let split_idx = match body_bytes.iter().position(|b| *b == b'&') {
1029        Some(split_idx) => split_idx,
1030        None => return error("Could not find ampersand in data"),
1031    };
1032    let (secret, cert_base64) = body_bytes.split_at(split_idx);
1033
1034    let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
1035    if secret != Some(*net_traits::PRIVILEGED_SECRET) {
1036        return error("Invalid secret sent. Ignoring request");
1037    }
1038
1039    let cert_bytes = match general_purpose::STANDARD_NO_PAD.decode(&cert_base64[1..]) {
1040        Ok(bytes) => bytes,
1041        Err(_) => return error("Could not decode certificate base64"),
1042    };
1043
1044    context
1045        .state
1046        .override_manager
1047        .add_override(&CertificateDer::from_slice(&cert_bytes).into_owned());
1048    Ok(())
1049}
1050
1051/// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch)
1052async fn scheme_fetch(
1053    fetch_params: &mut FetchParams,
1054    cache: &mut CorsCache,
1055    target: Target<'_>,
1056    done_chan: &mut DoneChannel,
1057    context: &FetchContext,
1058) -> Response {
1059    // Step 1: If fetchParams is canceled, then return the appropriate network error for fetchParams.
1060
1061    // Step 2: Let request be fetchParams’s request.
1062    let request = &mut fetch_params.request;
1063    let url_and_blob_lock = request.current_url_with_blob_claim();
1064
1065    let scheme = url_and_blob_lock.scheme();
1066    match scheme {
1067        "about" if url_and_blob_lock.path() == "blank" => {
1068            create_blank_reply(url_and_blob_lock.url(), request.timing_type())
1069        },
1070        "about" if url_and_blob_lock.path() == "memory" => {
1071            create_about_memory(url_and_blob_lock.url(), request.timing_type())
1072        },
1073
1074        "chrome" if url_and_blob_lock.path() == "allowcert" => {
1075            if let Err(error) = handle_allowcert_request(request, context) {
1076                warn!("Could not handle allowcert request: {error}");
1077            }
1078            create_blank_reply(url_and_blob_lock.url(), request.timing_type())
1079        },
1080
1081        "http" | "https" => {
1082            http_fetch(
1083                fetch_params,
1084                cache,
1085                false,
1086                false,
1087                false,
1088                target,
1089                done_chan,
1090                context,
1091            )
1092            .await
1093        },
1094
1095        _ => match context.protocols.get(scheme) {
1096            Some(handler) => handler.load(request, done_chan, context).await,
1097            None => Response::network_error(NetworkError::UnsupportedScheme),
1098        },
1099    }
1100}
1101
1102fn is_null_body_status(status: &HttpStatus) -> bool {
1103    matches!(
1104        status.try_code(),
1105        Some(StatusCode::SWITCHING_PROTOCOLS) |
1106            Some(StatusCode::NO_CONTENT) |
1107            Some(StatusCode::RESET_CONTENT) |
1108            Some(StatusCode::NOT_MODIFIED)
1109    )
1110}
1111
1112/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?>
1113pub fn should_be_blocked_due_to_nosniff(
1114    destination: Destination,
1115    response_headers: &HeaderMap,
1116) -> bool {
1117    // Step 1
1118    if !determine_nosniff(response_headers) {
1119        return false;
1120    }
1121
1122    // Step 2
1123    // Note: an invalid MIME type will produce a `None`.
1124    let mime_type = extract_mime_type_as_mime(response_headers);
1125
1126    /// <https://html.spec.whatwg.org/multipage/#scriptingLanguages>
1127    #[inline]
1128    fn is_javascript_mime_type(mime_type: &Mime) -> bool {
1129        let javascript_mime_types: [Mime; 16] = [
1130            "application/ecmascript".parse().unwrap(),
1131            "application/javascript".parse().unwrap(),
1132            "application/x-ecmascript".parse().unwrap(),
1133            "application/x-javascript".parse().unwrap(),
1134            "text/ecmascript".parse().unwrap(),
1135            "text/javascript".parse().unwrap(),
1136            "text/javascript1.0".parse().unwrap(),
1137            "text/javascript1.1".parse().unwrap(),
1138            "text/javascript1.2".parse().unwrap(),
1139            "text/javascript1.3".parse().unwrap(),
1140            "text/javascript1.4".parse().unwrap(),
1141            "text/javascript1.5".parse().unwrap(),
1142            "text/jscript".parse().unwrap(),
1143            "text/livescript".parse().unwrap(),
1144            "text/x-ecmascript".parse().unwrap(),
1145            "text/x-javascript".parse().unwrap(),
1146        ];
1147
1148        javascript_mime_types
1149            .iter()
1150            .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
1151    }
1152
1153    match mime_type {
1154        // Step 4
1155        Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type),
1156        // Step 5
1157        Some(ref mime_type) if destination == Destination::Style => {
1158            mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS
1159        },
1160
1161        None if destination == Destination::Style || destination.is_script_like() => true,
1162        // Step 6
1163        _ => false,
1164    }
1165}
1166
1167/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type?>
1168fn should_be_blocked_due_to_mime_type(
1169    destination: Destination,
1170    response_headers: &HeaderMap,
1171) -> bool {
1172    // Step 1: Let mimeType be the result of extracting a MIME type from response’s header list.
1173    let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) {
1174        Some(mime_type) => mime_type,
1175        // Step 2: If mimeType is failure, then return allowed.
1176        None => return false,
1177    };
1178
1179    // Step 3: Let destination be request’s destination.
1180    // Step 4: If destination is script-like and one of the following is true, then return blocked:
1181    //    - mimeType’s essence starts with "audio/", "image/", or "video/".
1182    //    - mimeType’s essence is "text/csv".
1183    // Step 5: Return allowed.
1184    destination.is_script_like() &&
1185        match mime_type.type_() {
1186            mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
1187            mime::TEXT if mime_type.subtype() == mime::CSV => true,
1188            _ => false,
1189        }
1190}
1191
1192/// <https://fetch.spec.whatwg.org/#block-bad-port>
1193pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
1194    // Step 1. Let url be request’s current URL.
1195    // NOTE: We receive the request url as an argument
1196
1197    // Step 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port, then return blocked.
1198    let is_http_scheme = matches!(url.scheme(), "http" | "https");
1199    let is_bad_port = url.port().is_some_and(is_bad_port);
1200    if is_http_scheme && is_bad_port {
1201        return true;
1202    }
1203
1204    // Step 3. Return allowed.
1205    false
1206}
1207
1208/// <https://w3c.github.io/webappsec-mixed-content/#should-block-fetch>
1209pub fn should_request_be_blocked_as_mixed_content(
1210    request: &Request,
1211    protocol_registry: &ProtocolRegistry,
1212) -> bool {
1213    // Step 1. Return allowed if one or more of the following conditions are met:
1214    // 1.1. Does settings prohibit mixed security contexts?
1215    // returns "Does Not Restrict Mixed Security Contexts" when applied to request’s client.
1216    if do_settings_prohibit_mixed_security_contexts(request) ==
1217        MixedSecurityProhibited::NotProhibited
1218    {
1219        return false;
1220    }
1221
1222    // 1.2. request’s URL is a potentially trustworthy URL.
1223    if is_url_potentially_trustworthy(protocol_registry, &request.current_url()) {
1224        return false;
1225    }
1226
1227    // 1.3. The user agent has been instructed to allow mixed content.
1228
1229    // 1.4. request’s destination is "document", and request’s target browsing context has
1230    // no parent browsing context.
1231    if request.destination == Destination::Document {
1232        // TODO: request's target browsing context has no parent browsing context
1233        return false;
1234    }
1235
1236    true
1237}
1238
1239/// <https://w3c.github.io/webappsec-mixed-content/#should-block-response>
1240pub fn should_response_be_blocked_as_mixed_content(
1241    request: &Request,
1242    response: &Response,
1243    protocol_registry: &ProtocolRegistry,
1244) -> bool {
1245    // Step 1. Return allowed if one or more of the following conditions are met:
1246    // 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content
1247    // when applied to request’s client.
1248    if do_settings_prohibit_mixed_security_contexts(request) ==
1249        MixedSecurityProhibited::NotProhibited
1250    {
1251        return false;
1252    }
1253
1254    // 1.2. response’s url is a potentially trustworthy URL.
1255    if response
1256        .actual_response()
1257        .url()
1258        .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
1259    {
1260        return false;
1261    }
1262
1263    // 1.3. TODO: The user agent has been instructed to allow mixed content.
1264
1265    // 1.4. request’s destination is "document", and request’s target browsing context
1266    // has no parent browsing context.
1267    if request.destination == Destination::Document {
1268        // TODO: if requests target browsing context has no parent browsing context
1269        return false;
1270    }
1271
1272    true
1273}
1274
1275/// <https://fetch.spec.whatwg.org/#bad-port>
1276fn is_bad_port(port: u16) -> bool {
1277    static BAD_PORTS: [u16; 78] = [
1278        1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
1279        102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
1280        427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
1281        993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
1282        6668, 6669, 6697, 10080,
1283    ];
1284
1285    BAD_PORTS.binary_search(&port).is_ok()
1286}
1287
1288// TODO : Investigate and need to revisit again
1289pub fn is_form_submission_request(request: &Request) -> bool {
1290    let content_type = request.headers.typed_get::<ContentType>();
1291    content_type.is_some_and(|ct| {
1292        let mime: Mime = ct.into();
1293        mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED
1294    })
1295}
1296
1297/// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request>
1298fn should_upgrade_request_to_potentially_trustworthy(
1299    request: &mut Request,
1300    context: &FetchContext,
1301) -> bool {
1302    fn should_upgrade_navigation_request(request: &Request) -> bool {
1303        // Step 2.1 If request is a form submission, skip the remaining substeps, and continue upgrading request.
1304        if is_form_submission_request(request) {
1305            return true;
1306        }
1307
1308        // Step 2.2 If request’s client's target browsing context is a nested browsing context,
1309        // skip the remaining substeps and continue upgrading request.
1310        if request
1311            .client
1312            .as_ref()
1313            .is_some_and(|client| client.is_nested_browsing_context)
1314        {
1315            return true;
1316        }
1317
1318        // Step 2.4
1319        // TODO : check for insecure navigation set after its implemention
1320
1321        // Step 2.5 Return without further modifying request
1322        false
1323    }
1324
1325    // Step 1. If request is a navigation request,
1326    if request.is_navigation_request() {
1327        // Append a header named Upgrade-Insecure-Requests with a value of 1 to
1328        // request’s header list if any of the following criteria are met:
1329        // * request’s URL is not a potentially trustworthy URL
1330        // * request’s URL's host is not a preloadable HSTS host
1331        if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
1332            request
1333                .current_url()
1334                .host_str()
1335                .is_none_or(|host| context.state.hsts_list.read().is_host_secure(host))
1336        {
1337            debug!("Appending the Upgrade-Insecure-Requests header to request’s header list");
1338            request
1339                .headers
1340                .insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1"));
1341        }
1342
1343        if !should_upgrade_navigation_request(request) {
1344            return false;
1345        }
1346    }
1347
1348    // Step 3. Let upgrade state be the result of executing
1349    // §4.2 Should insecure requests be upgraded for client? upon request's client.
1350    // Step 4. If upgrade state is "Do Not Upgrade", return without modifying request.
1351    request
1352        .client
1353        .as_ref()
1354        .is_some_and(|client| client.insecure_requests_policy == InsecureRequestsPolicy::Upgrade)
1355}
1356
1357#[derive(Debug, PartialEq)]
1358pub enum MixedSecurityProhibited {
1359    Prohibited,
1360    NotProhibited,
1361}
1362
1363/// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object>
1364fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
1365    if let Origin::Origin(ref origin) = request.origin {
1366        // Workers created from a data: url are secure if they were created from secure contexts
1367        let is_origin_data_url_worker = matches!(
1368            *origin,
1369            ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
1370        );
1371
1372        // Step 1. If settings’ origin is a potentially trustworthy origin,
1373        // then return "Prohibits Mixed Security Contexts".
1374        if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
1375            return MixedSecurityProhibited::Prohibited;
1376        }
1377    }
1378
1379    // Step 2.2. For each navigable navigable in document’s ancestor navigables:
1380    // Step 2.2.1. If navigable’s active document's origin is a potentially trustworthy origin,
1381    // then return "Prohibits Mixed Security Contexts".
1382    if request.has_trustworthy_ancestor_origin {
1383        return MixedSecurityProhibited::Prohibited;
1384    }
1385
1386    MixedSecurityProhibited::NotProhibited
1387}
1388
1389/// <https://w3c.github.io/webappsec-mixed-content/#upgrade-algorithm>
1390fn should_upgrade_mixed_content_request(
1391    request: &Request,
1392    protocol_registry: &ProtocolRegistry,
1393) -> bool {
1394    let url = request.url();
1395    // Step 1.1 : request’s URL is a potentially trustworthy URL.
1396    if is_url_potentially_trustworthy(protocol_registry, &url) {
1397        return false;
1398    }
1399
1400    // Step 1.2 : request’s URL’s host is an IP address.
1401    match url.host() {
1402        Some(Host::Ipv4(_)) | Some(Host::Ipv6(_)) => return false,
1403        _ => (),
1404    }
1405
1406    // Step 1.3
1407    if do_settings_prohibit_mixed_security_contexts(request) ==
1408        MixedSecurityProhibited::NotProhibited
1409    {
1410        return false;
1411    }
1412
1413    // Step 1.4 : request’s destination is not "image", "audio", or "video".
1414    if !matches!(
1415        request.destination,
1416        Destination::Audio | Destination::Image | Destination::Video
1417    ) {
1418        return false;
1419    }
1420
1421    // Step 1.5 : request’s destination is "image" and request’s initiator is "imageset".
1422    if request.destination == Destination::Image && request.initiator == Initiator::ImageSet {
1423        return false;
1424    }
1425
1426    true
1427}