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