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