Skip to main content

net/fetch/
methods.rs

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