Skip to main content

net/fetch/
methods.rs

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