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