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