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