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