Skip to main content

net/fetch/
methods.rs

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