Skip to main content

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(res) => res,
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 == current_url.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.clone_from(&request.url_list)
730        }
731
732        // Step 17. Set internalResponse’s redirect taint to request’s redirect-taint.
733        internal_response.redirect_taint = request.redirect_taint_for_request();
734
735        // Step 19. If response is not a network error and any of the following returns blocked
736        // * should internalResponse to request be blocked as mixed content
737        // * should internalResponse to request be blocked by Content Security Policy
738        // * should internalResponse to request be blocked due to its MIME type
739        // * should internalResponse to request be blocked due to nosniff
740        let mut blocked_error_response;
741
742        let internal_response = if should_replace_with_nosniff_error {
743            // Defer rebinding result
744            blocked_error_response = Response::network_error(NetworkError::Nosniff);
745            &blocked_error_response
746        } else if should_replace_with_mime_type_error {
747            // Defer rebinding result
748            blocked_error_response =
749                Response::network_error(NetworkError::MimeType("Blocked by MIME type".into()));
750            &blocked_error_response
751        } else if should_replace_with_mixed_content {
752            blocked_error_response = Response::network_error(NetworkError::MixedContent);
753            &blocked_error_response
754        } else if should_replace_with_csp_error {
755            blocked_error_response = Response::network_error(NetworkError::ContentSecurityPolicy);
756            &blocked_error_response
757        } else {
758            internal_response
759        };
760
761        // Step 20. If response’s type is "opaque", internalResponse’s status is 206, internalResponse’s
762        // range-requested flag is set, and request’s header list does not contain `Range`, then set
763        // response and internalResponse to a network error.
764        // Also checking if internal response is a network error to prevent crash from attemtping to
765        // read status of a network error if we blocked the request above.
766        let internal_response = if !internal_response.is_network_error() &&
767            response_type == ResponseType::Opaque &&
768            internal_response.status.code() == StatusCode::PARTIAL_CONTENT &&
769            internal_response.range_requested &&
770            !request.headers.contains_key(RANGE)
771        {
772            // Defer rebinding result
773            blocked_error_response =
774                Response::network_error(NetworkError::PartialResponseToNonRangeRequestError);
775            &blocked_error_response
776        } else {
777            internal_response
778        };
779
780        // Step 21. If response is not a network error and either request’s method is `HEAD` or `CONNECT`,
781        // or internalResponse’s status is a null body status, set internalResponse’s body to null and
782        // disregard any enqueuing toward it (if any).
783        // NOTE: We check `internal_response` since we did not mutate `response` in the previous steps.
784        let not_network_error = !response_is_network_error && !internal_response.is_network_error();
785        if not_network_error &&
786            (is_null_body_status(&internal_response.status) ||
787                matches!(request.method, Method::HEAD | Method::CONNECT))
788        {
789            // when Fetch is used only asynchronously, we will need to make sure
790            // that nothing tries to write to the body at this point
791            let mut body = internal_response.body.lock();
792            *body = ResponseBody::Empty;
793        }
794
795        internal_response.get_network_error().cloned()
796    };
797
798    // Execute deferred rebinding of response.
799    let mut response = if let Some(error) = internal_error {
800        Response::network_error(error)
801    } else {
802        response
803    };
804
805    // Step 19. If response is not a network error and any of the following returns blocked
806    let mut response_loaded = false;
807    let mut response = if !response.is_network_error() && !request.integrity_metadata.is_empty() {
808        // Step 19.1.
809        wait_for_response(request, &mut response, target, done_chan, context).await;
810        response_loaded = true;
811
812        // Step 19.2.
813        let integrity_metadata = &request.integrity_metadata;
814        if response.termination_reason.is_none() &&
815            !is_response_integrity_valid(integrity_metadata, &response)
816        {
817            Response::network_error(NetworkError::SubresourceIntegrity)
818        } else {
819            response
820        }
821    } else {
822        response
823    };
824
825    // Step 20.
826    if request.synchronous {
827        // process_response is not supposed to be used
828        // by sync fetch, but we overload it here for simplicity
829        target.process_response(request, &response);
830        if !response_loaded {
831            wait_for_response(request, &mut response, target, done_chan, context).await;
832        }
833        // overloaded similarly to process_response
834        target.process_response_eof(request, &response);
835        return response;
836    }
837
838    // Step 21.
839    if request.body.is_some() && matches!(current_scheme, "http" | "https") {
840        // XXXManishearth: We actually should be calling process_request
841        // in http_network_fetch. However, we can't yet follow the request
842        // upload progress, so I'm keeping it here for now and pretending
843        // the body got sent in one chunk
844        target.process_request_body(request);
845    }
846
847    // Step 22.
848    target.process_response(request, &response);
849    // Send Response to Devtools
850    send_response_to_devtools(request, context, &response, None);
851    send_security_info_to_devtools(request, context, &response);
852
853    // Step 23.
854    if !response_loaded {
855        wait_for_response(request, &mut response, target, done_chan, context).await;
856    }
857
858    // Step 24.
859    target.process_response_eof(request, &response);
860    // Send Response to Devtools
861    // This is done after process_response_eof to ensure that the body is fully
862    // processed before sending the response to Devtools.
863    send_response_to_devtools(request, context, &response, None);
864
865    context
866        .state
867        .http_cache
868        .update_awaiting_consumers(request, &response)
869        .await;
870
871    // Steps 25-27.
872    // TODO: remove this line when only asynchronous fetches are used
873    response
874}
875
876async fn wait_for_response(
877    request: &Request,
878    response: &mut Response,
879    target: Target<'_>,
880    done_chan: &mut DoneChannel,
881    context: &FetchContext,
882) {
883    if let Some(ref mut ch) = *done_chan {
884        let mut devtools_body = context.devtools_chan.as_ref().map(|_| Vec::new());
885        loop {
886            match ch.1.recv().await {
887                Some(Data::Payload(vec)) => {
888                    if let Some(body) = devtools_body.as_mut() {
889                        body.extend(&vec);
890                    }
891                    target.process_response_chunk(request, vec);
892                },
893                Some(Data::Error(network_error)) => {
894                    if network_error == NetworkError::DecompressionError {
895                        response.termination_reason = Some(TerminationReason::Fatal);
896                    }
897                    response.set_network_error(network_error);
898
899                    break;
900                },
901                Some(Data::Done) => {
902                    send_response_to_devtools(request, context, response, devtools_body);
903                    break;
904                },
905                Some(Data::Cancelled) => {
906                    response.aborted.store(true, Ordering::Release);
907                    break;
908                },
909                _ => {
910                    panic!("fetch worker should always send Done before terminating");
911                },
912            }
913        }
914    } else {
915        match *response.actual_response().body.lock() {
916            ResponseBody::Done(ref vec) if !vec.is_empty() => {
917                // in case there was no channel to wait for, the body was
918                // obtained synchronously via scheme_fetch for data/file/about/etc
919                // We should still send the body across as a chunk
920                target.process_response_chunk(request, vec.clone());
921                if context.devtools_chan.is_some() {
922                    // Now that we've replayed the entire cached body,
923                    // notify the DevTools server with the full Response.
924                    send_response_to_devtools(request, context, response, Some(vec.clone()));
925                }
926            },
927            ResponseBody::Done(_) | ResponseBody::Empty => {},
928            _ => unreachable!(),
929        }
930    }
931}
932
933/// Range header start and end values.
934pub enum RangeRequestBounds {
935    /// The range bounds are known and set to final values.
936    Final(RelativePos),
937    /// We need extra information to set the range bounds.
938    /// i.e. buffer or file size.
939    Pending(u64),
940}
941
942impl RangeRequestBounds {
943    pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, &'static str> {
944        match self {
945            RangeRequestBounds::Final(pos) => {
946                if let Some(len) = len {
947                    if pos.start <= len as i64 {
948                        return Ok(*pos);
949                    }
950                }
951                Err("Tried to process RangeRequestBounds::Final without len")
952            },
953            RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
954                if let Some(len) = len {
955                    Some((len - u64::min(len, *offset)) as i64)
956                } else {
957                    Some(0)
958                },
959                None,
960            )),
961        }
962    }
963}
964
965fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
966    let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
967    response
968        .headers
969        .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
970    *response.body.lock() = ResponseBody::Done(vec![]);
971    response.status = HttpStatus::default();
972    response
973}
974
975fn create_about_memory(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
976    let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
977    response
978        .headers
979        .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
980    *response.body.lock() = ResponseBody::Done(resources::read_bytes(Resource::AboutMemoryHTML));
981    response.status = HttpStatus::default();
982    response
983}
984
985/// Handle a request from the user interface to ignore validation errors for a certificate.
986fn handle_allowcert_request(request: &mut Request, context: &FetchContext) -> io::Result<()> {
987    let error = |string| Err(io::Error::other(string));
988
989    let body = match request.body.as_mut() {
990        Some(body) => body,
991        None => return error("No body found"),
992    };
993
994    let stream = body.clone_stream();
995    let mut stream = stream.lock();
996    let (body_chan, body_port) = ipc::channel().unwrap();
997    let Some(chunk_requester) = stream.as_mut() else {
998        log::error!(
999            "Could not connect to the request body stream because it has already been closed."
1000        );
1001        return Err(std::io::Error::other("Could not send BodyChunkRequest"));
1002    };
1003    chunk_requester
1004        .send(BodyChunkRequest::Connect(body_chan))
1005        .map_err(|error| {
1006            log::error!(
1007                "Could not connect to the request body stream because it has already been closed: {error}"
1008            );
1009            std::io::Error::other("Could not connect to request body stream")
1010        })?;
1011    chunk_requester
1012        .send(BodyChunkRequest::Chunk)
1013        .map_err(|error| {
1014            log::error!(
1015                "Could not request the first request body chunk because the body stream has already been closed: {error}"
1016            );
1017            std::io::Error::other("Could not request request body chunk")
1018        })?;
1019    let body_bytes = match body_port.recv().ok() {
1020        Some(BodyChunkResponse::Chunk(bytes)) => bytes,
1021        _ => return error("Certificate not sent in a single chunk"),
1022    };
1023
1024    let split_idx = match body_bytes.iter().position(|b| *b == b'&') {
1025        Some(split_idx) => split_idx,
1026        None => return error("Could not find ampersand in data"),
1027    };
1028    let (secret, cert_base64) = body_bytes.split_at(split_idx);
1029
1030    let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
1031    if secret != Some(*net_traits::PRIVILEGED_SECRET) {
1032        return error("Invalid secret sent. Ignoring request");
1033    }
1034
1035    let cert_bytes = match general_purpose::STANDARD_NO_PAD.decode(&cert_base64[1..]) {
1036        Ok(bytes) => bytes,
1037        Err(_) => return error("Could not decode certificate base64"),
1038    };
1039
1040    context
1041        .state
1042        .override_manager
1043        .add_override(&CertificateDer::from_slice(&cert_bytes).into_owned());
1044    Ok(())
1045}
1046
1047/// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch)
1048async fn scheme_fetch(
1049    fetch_params: &mut FetchParams,
1050    cache: &mut CorsCache,
1051    target: Target<'_>,
1052    done_chan: &mut DoneChannel,
1053    context: &FetchContext,
1054) -> Response {
1055    // Step 1: If fetchParams is canceled, then return the appropriate network error for fetchParams.
1056
1057    // Step 2: Let request be fetchParams’s request.
1058    let request = &mut fetch_params.request;
1059    let url = request.current_url();
1060
1061    let scheme = url.scheme();
1062    match scheme {
1063        "about" if url.path() == "blank" => create_blank_reply(url, request.timing_type()),
1064        "about" if url.path() == "memory" => create_about_memory(url, request.timing_type()),
1065
1066        "chrome" if url.path() == "allowcert" => {
1067            if let Err(error) = handle_allowcert_request(request, context) {
1068                warn!("Could not handle allowcert request: {error}");
1069            }
1070            create_blank_reply(url, request.timing_type())
1071        },
1072
1073        "http" | "https" => {
1074            http_fetch(
1075                fetch_params,
1076                cache,
1077                false,
1078                false,
1079                false,
1080                target,
1081                done_chan,
1082                context,
1083            )
1084            .await
1085        },
1086
1087        _ => match context.protocols.get(scheme) {
1088            Some(handler) => handler.load(request, done_chan, context).await,
1089            None => Response::network_error(NetworkError::UnsupportedScheme),
1090        },
1091    }
1092}
1093
1094fn is_null_body_status(status: &HttpStatus) -> bool {
1095    matches!(
1096        status.try_code(),
1097        Some(StatusCode::SWITCHING_PROTOCOLS) |
1098            Some(StatusCode::NO_CONTENT) |
1099            Some(StatusCode::RESET_CONTENT) |
1100            Some(StatusCode::NOT_MODIFIED)
1101    )
1102}
1103
1104/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?>
1105pub fn should_be_blocked_due_to_nosniff(
1106    destination: Destination,
1107    response_headers: &HeaderMap,
1108) -> bool {
1109    // Step 1
1110    if !determine_nosniff(response_headers) {
1111        return false;
1112    }
1113
1114    // Step 2
1115    // Note: an invalid MIME type will produce a `None`.
1116    let mime_type = extract_mime_type_as_mime(response_headers);
1117
1118    /// <https://html.spec.whatwg.org/multipage/#scriptingLanguages>
1119    #[inline]
1120    fn is_javascript_mime_type(mime_type: &Mime) -> bool {
1121        let javascript_mime_types: [Mime; 16] = [
1122            "application/ecmascript".parse().unwrap(),
1123            "application/javascript".parse().unwrap(),
1124            "application/x-ecmascript".parse().unwrap(),
1125            "application/x-javascript".parse().unwrap(),
1126            "text/ecmascript".parse().unwrap(),
1127            "text/javascript".parse().unwrap(),
1128            "text/javascript1.0".parse().unwrap(),
1129            "text/javascript1.1".parse().unwrap(),
1130            "text/javascript1.2".parse().unwrap(),
1131            "text/javascript1.3".parse().unwrap(),
1132            "text/javascript1.4".parse().unwrap(),
1133            "text/javascript1.5".parse().unwrap(),
1134            "text/jscript".parse().unwrap(),
1135            "text/livescript".parse().unwrap(),
1136            "text/x-ecmascript".parse().unwrap(),
1137            "text/x-javascript".parse().unwrap(),
1138        ];
1139
1140        javascript_mime_types
1141            .iter()
1142            .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
1143    }
1144
1145    match mime_type {
1146        // Step 4
1147        Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type),
1148        // Step 5
1149        Some(ref mime_type) if destination == Destination::Style => {
1150            mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS
1151        },
1152
1153        None if destination == Destination::Style || destination.is_script_like() => true,
1154        // Step 6
1155        _ => false,
1156    }
1157}
1158
1159/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type?>
1160fn should_be_blocked_due_to_mime_type(
1161    destination: Destination,
1162    response_headers: &HeaderMap,
1163) -> bool {
1164    // Step 1: Let mimeType be the result of extracting a MIME type from response’s header list.
1165    let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) {
1166        Some(mime_type) => mime_type,
1167        // Step 2: If mimeType is failure, then return allowed.
1168        None => return false,
1169    };
1170
1171    // Step 3: Let destination be request’s destination.
1172    // Step 4: If destination is script-like and one of the following is true, then return blocked:
1173    //    - mimeType’s essence starts with "audio/", "image/", or "video/".
1174    //    - mimeType’s essence is "text/csv".
1175    // Step 5: Return allowed.
1176    destination.is_script_like() &&
1177        match mime_type.type_() {
1178            mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
1179            mime::TEXT if mime_type.subtype() == mime::CSV => true,
1180            _ => false,
1181        }
1182}
1183
1184/// <https://fetch.spec.whatwg.org/#block-bad-port>
1185pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
1186    // Step 1. Let url be request’s current URL.
1187    // NOTE: We receive the request url as an argument
1188
1189    // Step 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port, then return blocked.
1190    let is_http_scheme = matches!(url.scheme(), "http" | "https");
1191    let is_bad_port = url.port().is_some_and(is_bad_port);
1192    if is_http_scheme && is_bad_port {
1193        return true;
1194    }
1195
1196    // Step 3. Return allowed.
1197    false
1198}
1199
1200/// <https://w3c.github.io/webappsec-mixed-content/#should-block-fetch>
1201pub fn should_request_be_blocked_as_mixed_content(
1202    request: &Request,
1203    protocol_registry: &ProtocolRegistry,
1204) -> bool {
1205    // Step 1. Return allowed if one or more of the following conditions are met:
1206    // 1.1. Does settings prohibit mixed security contexts?
1207    // returns "Does Not Restrict Mixed Security Contexts" when applied to request’s client.
1208    if do_settings_prohibit_mixed_security_contexts(request) ==
1209        MixedSecurityProhibited::NotProhibited
1210    {
1211        return false;
1212    }
1213
1214    // 1.2. request’s URL is a potentially trustworthy URL.
1215    if is_url_potentially_trustworthy(protocol_registry, &request.current_url()) {
1216        return false;
1217    }
1218
1219    // 1.3. The user agent has been instructed to allow mixed content.
1220
1221    // 1.4. request’s destination is "document", and request’s target browsing context has
1222    // no parent browsing context.
1223    if request.destination == Destination::Document {
1224        // TODO: request's target browsing context has no parent browsing context
1225        return false;
1226    }
1227
1228    true
1229}
1230
1231/// <https://w3c.github.io/webappsec-mixed-content/#should-block-response>
1232pub fn should_response_be_blocked_as_mixed_content(
1233    request: &Request,
1234    response: &Response,
1235    protocol_registry: &ProtocolRegistry,
1236) -> bool {
1237    // Step 1. Return allowed if one or more of the following conditions are met:
1238    // 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content
1239    // when applied to request’s client.
1240    if do_settings_prohibit_mixed_security_contexts(request) ==
1241        MixedSecurityProhibited::NotProhibited
1242    {
1243        return false;
1244    }
1245
1246    // 1.2. response’s url is a potentially trustworthy URL.
1247    if response
1248        .actual_response()
1249        .url()
1250        .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
1251    {
1252        return false;
1253    }
1254
1255    // 1.3. TODO: The user agent has been instructed to allow mixed content.
1256
1257    // 1.4. request’s destination is "document", and request’s target browsing context
1258    // has no parent browsing context.
1259    if request.destination == Destination::Document {
1260        // TODO: if requests target browsing context has no parent browsing context
1261        return false;
1262    }
1263
1264    true
1265}
1266
1267/// <https://fetch.spec.whatwg.org/#bad-port>
1268fn is_bad_port(port: u16) -> bool {
1269    static BAD_PORTS: [u16; 78] = [
1270        1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
1271        102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
1272        427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
1273        993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
1274        6668, 6669, 6697, 10080,
1275    ];
1276
1277    BAD_PORTS.binary_search(&port).is_ok()
1278}
1279
1280// TODO : Investigate and need to revisit again
1281pub fn is_form_submission_request(request: &Request) -> bool {
1282    let content_type = request.headers.typed_get::<ContentType>();
1283    content_type.is_some_and(|ct| {
1284        let mime: Mime = ct.into();
1285        mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED
1286    })
1287}
1288
1289/// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request>
1290fn should_upgrade_request_to_potentially_trustworthy(
1291    request: &mut Request,
1292    context: &FetchContext,
1293) -> bool {
1294    fn should_upgrade_navigation_request(request: &Request) -> bool {
1295        // Step 2.1 If request is a form submission, skip the remaining substeps, and continue upgrading request.
1296        if is_form_submission_request(request) {
1297            return true;
1298        }
1299
1300        // Step 2.2 If request’s client's target browsing context is a nested browsing context,
1301        // skip the remaining substeps and continue upgrading request.
1302        if request
1303            .client
1304            .as_ref()
1305            .is_some_and(|client| client.is_nested_browsing_context)
1306        {
1307            return true;
1308        }
1309
1310        // Step 2.4
1311        // TODO : check for insecure navigation set after its implemention
1312
1313        // Step 2.5 Return without further modifying request
1314        false
1315    }
1316
1317    // Step 1. If request is a navigation request,
1318    if request.is_navigation_request() {
1319        // Append a header named Upgrade-Insecure-Requests with a value of 1 to
1320        // request’s header list if any of the following criteria are met:
1321        // * request’s URL is not a potentially trustworthy URL
1322        // * request’s URL's host is not a preloadable HSTS host
1323        if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
1324            request
1325                .current_url()
1326                .host_str()
1327                .is_none_or(|host| context.state.hsts_list.read().is_host_secure(host))
1328        {
1329            debug!("Appending the Upgrade-Insecure-Requests header to request’s header list");
1330            request
1331                .headers
1332                .insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1"));
1333        }
1334
1335        if !should_upgrade_navigation_request(request) {
1336            return false;
1337        }
1338    }
1339
1340    // Step 3. Let upgrade state be the result of executing
1341    // §4.2 Should insecure requests be upgraded for client? upon request's client.
1342    // Step 4. If upgrade state is "Do Not Upgrade", return without modifying request.
1343    request
1344        .client
1345        .as_ref()
1346        .is_some_and(|client| client.insecure_requests_policy == InsecureRequestsPolicy::Upgrade)
1347}
1348
1349#[derive(Debug, PartialEq)]
1350pub enum MixedSecurityProhibited {
1351    Prohibited,
1352    NotProhibited,
1353}
1354
1355/// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object>
1356fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
1357    if let Origin::Origin(ref origin) = request.origin {
1358        // Workers created from a data: url are secure if they were created from secure contexts
1359        let is_origin_data_url_worker = matches!(
1360            *origin,
1361            ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
1362        );
1363
1364        // Step 1. If settings’ origin is a potentially trustworthy origin,
1365        // then return "Prohibits Mixed Security Contexts".
1366        if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
1367            return MixedSecurityProhibited::Prohibited;
1368        }
1369    }
1370
1371    // Step 2.2. For each navigable navigable in document’s ancestor navigables:
1372    // Step 2.2.1. If navigable’s active document's origin is a potentially trustworthy origin,
1373    // then return "Prohibits Mixed Security Contexts".
1374    if request.has_trustworthy_ancestor_origin {
1375        return MixedSecurityProhibited::Prohibited;
1376    }
1377
1378    MixedSecurityProhibited::NotProhibited
1379}
1380
1381/// <https://w3c.github.io/webappsec-mixed-content/#upgrade-algorithm>
1382fn should_upgrade_mixed_content_request(
1383    request: &Request,
1384    protocol_registry: &ProtocolRegistry,
1385) -> bool {
1386    let url = request.url();
1387    // Step 1.1 : request’s URL is a potentially trustworthy URL.
1388    if is_url_potentially_trustworthy(protocol_registry, &url) {
1389        return false;
1390    }
1391
1392    // Step 1.2 : request’s URL’s host is an IP address.
1393    match url.host() {
1394        Some(Host::Ipv4(_)) | Some(Host::Ipv6(_)) => return false,
1395        _ => (),
1396    }
1397
1398    // Step 1.3
1399    if do_settings_prohibit_mixed_security_contexts(request) ==
1400        MixedSecurityProhibited::NotProhibited
1401    {
1402        return false;
1403    }
1404
1405    // Step 1.4 : request’s destination is not "image", "audio", or "video".
1406    if !matches!(
1407        request.destination,
1408        Destination::Audio | Destination::Image | Destination::Video
1409    ) {
1410        return false;
1411    }
1412
1413    // Step 1.5 : request’s destination is "image" and request’s initiator is "imageset".
1414    if request.destination == Destination::Image && request.initiator == Initiator::ImageSet {
1415        return false;
1416    }
1417
1418    true
1419}