net/fetch/
methods.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::{io, mem, str};
8
9use base64::Engine as _;
10use base64::engine::general_purpose;
11use content_security_policy as csp;
12use crossbeam_channel::Sender;
13use devtools_traits::DevtoolsControlMsg;
14use embedder_traits::resources::{self, Resource};
15use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt};
16use http::header::{self, HeaderMap, HeaderName, RANGE};
17use http::{HeaderValue, Method, StatusCode};
18use ipc_channel::ipc::{self, 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 parking_lot::Mutex;
37use rustls_pki_types::CertificateDer;
38use serde::{Deserialize, Serialize};
39use servo_arc::Arc as ServoArc;
40use servo_url::{Host, ImmutableOrigin, ServoUrl};
41use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender};
42
43use crate::connector::CACertificates;
44use crate::fetch::cors_cache::CorsCache;
45use crate::fetch::fetch_params::{FetchParams, PreloadResponseCandidate};
46use crate::fetch::headers::determine_nosniff;
47use crate::filemanager_thread::FileManager;
48use crate::http_loader::{
49    HttpState, determine_requests_referrer, http_fetch, send_early_httprequest_to_devtools,
50    send_response_to_devtools, set_default_accept,
51};
52use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy};
53use crate::request_interceptor::RequestInterceptor;
54use crate::subresource_integrity::is_response_integrity_valid;
55
56const PARTIAL_RESPONSE_TO_NON_RANGE_REQUEST_ERROR: &str = "Refusing to provide partial response\
57from earlier ranged request to API that did not make a range request";
58
59pub type Target<'a> = &'a mut (dyn FetchTaskTarget + Send);
60
61#[derive(Clone, Deserialize, Serialize)]
62pub enum Data {
63    Payload(Vec<u8>),
64    Done,
65    Cancelled,
66}
67
68pub struct WebSocketChannel {
69    pub sender: IpcSender<WebSocketNetworkEvent>,
70    pub receiver: Option<IpcReceiver<WebSocketDomAction>>,
71}
72
73impl WebSocketChannel {
74    pub fn new(
75        sender: IpcSender<WebSocketNetworkEvent>,
76        receiver: Option<IpcReceiver<WebSocketDomAction>>,
77    ) -> Self {
78        Self { sender, receiver }
79    }
80}
81
82#[derive(Clone)]
83pub struct FetchContext {
84    pub state: Arc<HttpState>,
85    pub user_agent: String,
86    pub devtools_chan: Option<Arc<Mutex<Sender<DevtoolsControlMsg>>>>,
87    pub filemanager: Arc<Mutex<FileManager>>,
88    pub file_token: FileTokenCheck,
89    pub request_interceptor: Arc<Mutex<RequestInterceptor>>,
90    pub cancellation_listener: Arc<CancellationListener>,
91    pub timing: ServoArc<Mutex<ResourceFetchTiming>>,
92    pub protocols: Arc<ProtocolRegistry>,
93    pub websocket_chan: Option<Arc<Mutex<WebSocketChannel>>>,
94    pub ca_certificates: CACertificates,
95    pub ignore_certificate_errors: bool,
96}
97
98#[derive(Default)]
99pub struct CancellationListener {
100    cancelled: AtomicBool,
101}
102
103impl CancellationListener {
104    pub(crate) fn cancelled(&self) -> bool {
105        self.cancelled.load(Ordering::Relaxed)
106    }
107
108    pub(crate) fn cancel(&self) {
109        self.cancelled.store(true, Ordering::Relaxed)
110    }
111}
112pub type DoneChannel = Option<(TokioSender<Data>, TokioReceiver<Data>)>;
113
114/// [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
115pub async fn fetch(request: Request, target: Target<'_>, context: &FetchContext) -> Response {
116    // Steps 7,4 of https://w3c.github.io/resource-timing/#processing-model
117    // rev order okay since spec says they're equal - https://w3c.github.io/resource-timing/#dfn-starttime
118    {
119        let mut timing_guard = context.timing.lock();
120        timing_guard.set_attribute(ResourceAttribute::FetchStart);
121        timing_guard.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart));
122    }
123    fetch_with_cors_cache(request, &mut CorsCache::default(), target, context).await
124}
125
126/// Continuation of fetch from step 8.
127///
128/// <https://fetch.spec.whatwg.org#concept-fetch>
129pub async fn fetch_with_cors_cache(
130    request: Request,
131    cache: &mut CorsCache,
132    target: Target<'_>,
133    context: &FetchContext,
134) -> Response {
135    // Step 8. Let fetchParams be a new fetch params whose request is request
136    let mut fetch_params = FetchParams::new(request);
137    let request = &mut fetch_params.request;
138
139    // Step 4. Populate request from client given request.
140    request.populate_request_from_client();
141
142    // Step 5. If request’s client is non-null, then:
143    // TODO
144    // Step 5.1. Set taskDestination to request’s client’s global object.
145    // TODO
146    // Step 5.2. Set crossOriginIsolatedCapability to request’s client’s cross-origin isolated capability.
147    // TODO
148
149    // Step 10. If all of the following conditions are true:
150    if
151    // - request’s URL’s scheme is an HTTP(S) scheme
152    matches!(request.current_url().scheme(), "http" | "https")
153        // - request’s mode is "same-origin", "cors", or "no-cors"
154        && matches!(request.mode, RequestMode::SameOrigin | RequestMode::CorsMode | RequestMode::NoCors)
155        // - request’s method is `GET`
156        && matches!(request.method, Method::GET)
157        // - request’s unsafe-request flag is not set or request’s header list is empty
158        && (!request.unsafe_request || request.headers.is_empty())
159    {
160        // - request’s client is not null, and request’s client’s global object is a Window object
161        if let Some(client) = request.client.as_ref() {
162            // Step 10.1. Assert: request’s origin is same origin with request’s client’s origin.
163            assert!(request.origin == client.origin);
164            // Step 10.2. Let onPreloadedResponseAvailable be an algorithm that runs the
165            // following step given a response response: set fetchParams’s preloaded response candidate to response.
166            let on_preloaded_response_available = |response| {
167                fetch_params.preload_response_candidate =
168                    PreloadResponseCandidate::Response(Box::new(response))
169            };
170            // Step 10.3. Let foundPreloadedResource be the result of invoking consume a preloaded resource
171            // for request’s client, given request’s URL, request’s destination, request’s mode,
172            // request’s credentials mode, request’s integrity metadata, and onPreloadedResponseAvailable.
173            let found_preloaded_resource =
174                client.consume_preloaded_resource(request, on_preloaded_response_available);
175            // Step 10.4. If foundPreloadedResource is true and fetchParams’s preloaded response candidate is null,
176            // then set fetchParams’s preloaded response candidate to "pending".
177            if found_preloaded_resource &&
178                matches!(
179                    fetch_params.preload_response_candidate,
180                    PreloadResponseCandidate::None
181                )
182            {
183                fetch_params.preload_response_candidate = PreloadResponseCandidate::Pending;
184            }
185        }
186    }
187
188    // Step 11. If request’s header list does not contain `Accept`, then:
189    set_default_accept(request);
190
191    // Step 12. If request’s header list does not contain `Accept-Language`, then user agents should
192    // append (`Accept-Language, an appropriate header value) to request’s header list.
193    set_default_accept_language(&mut request.headers);
194
195    // Step 15. If request’s internal priority is null, then use request’s priority, initiator,
196    // destination, and render-blocking in an implementation-defined manner to set request’s
197    // internal priority to an implementation-defined object.
198    // TODO: figure out what a Priority object is.
199
200    // Step 16: If request is a subresource request, then:
201    if request.is_subresource_request() {
202        // TODO: requires keepalive.
203    }
204
205    // Step 17: Run main fetch given fetchParams.
206    main_fetch(&mut fetch_params, cache, false, target, &mut None, context).await
207
208    // Step 18: Return fetchParams’s controller.
209    // TODO: We don't implement fetchParams as defined in the spec
210}
211
212pub(crate) fn convert_request_to_csp_request(request: &Request) -> Option<csp::Request> {
213    let origin = match &request.origin {
214        Origin::Client => return None,
215        Origin::Origin(origin) => origin,
216    };
217
218    // We need to retroactively upgrade the ws URL if the rewritten http URL was upgraded to a secure scheme.
219    // https://github.com/w3c/webappsec-csp/issues/532
220    let mut original_url = request.original_url();
221    if original_url.scheme() == "ws" && request.url().scheme() == "https" {
222        original_url.as_mut_url().set_scheme("wss").unwrap();
223    }
224
225    let csp_request = csp::Request {
226        url: original_url.into_url(),
227        origin: origin.clone().into_url_origin(),
228        redirect_count: request.redirect_count,
229        destination: request.destination,
230        initiator: match request.initiator {
231            Initiator::Download => csp::Initiator::Download,
232            Initiator::ImageSet => csp::Initiator::ImageSet,
233            Initiator::Manifest => csp::Initiator::Manifest,
234            Initiator::Prefetch => csp::Initiator::Prefetch,
235            _ => csp::Initiator::None,
236        },
237        nonce: request.cryptographic_nonce_metadata.clone(),
238        integrity_metadata: request.integrity_metadata.clone(),
239        parser_metadata: match request.parser_metadata {
240            ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted,
241            ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted,
242            ParserMetadata::Default => csp::ParserMetadata::None,
243        },
244    };
245    Some(csp_request)
246}
247
248/// <https://www.w3.org/TR/CSP/#should-block-request>
249pub fn should_request_be_blocked_by_csp(
250    csp_request: &csp::Request,
251    policy_container: &PolicyContainer,
252) -> (csp::CheckResult, Vec<csp::Violation>) {
253    policy_container
254        .csp_list
255        .as_ref()
256        .map(|c| c.should_request_be_blocked(csp_request))
257        .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
258}
259
260/// <https://www.w3.org/TR/CSP/#report-for-request>
261pub fn report_violations_for_request_by_csp(
262    csp_request: &csp::Request,
263    policy_container: &PolicyContainer,
264) -> Vec<csp::Violation> {
265    policy_container
266        .csp_list
267        .as_ref()
268        .map(|c| c.report_violations_for_request(csp_request))
269        .unwrap_or_default()
270}
271
272fn should_response_be_blocked_by_csp(
273    csp_request: &csp::Request,
274    response: &Response,
275    policy_container: &PolicyContainer,
276) -> (csp::CheckResult, Vec<csp::Violation>) {
277    if response.is_network_error() {
278        return (csp::CheckResult::Allowed, Vec::new());
279    }
280    let csp_response = csp::Response {
281        url: response
282            .actual_response()
283            .url()
284            .cloned()
285            // NOTE(pylbrecht): for WebSocket connections, the URL scheme is converted to http(s)
286            // to integrate with fetch(). We need to convert it back to ws(s) to get valid CSP
287            // checks.
288            // https://github.com/w3c/webappsec-csp/issues/532
289            .map(|mut url| {
290                match csp_request.url.scheme() {
291                    "ws" | "wss" => {
292                        url.as_mut_url()
293                            .set_scheme(csp_request.url.scheme())
294                            .expect("failed to set URL scheme");
295                    },
296                    _ => {},
297                };
298                url
299            })
300            .expect("response must have a url")
301            .into_url(),
302        redirect_count: csp_request.redirect_count,
303    };
304    policy_container
305        .csp_list
306        .as_ref()
307        .map(|c| c.should_response_to_request_be_blocked(csp_request, &csp_response))
308        .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
309}
310
311/// [Main fetch](https://fetch.spec.whatwg.org/#concept-main-fetch)
312pub async fn main_fetch(
313    fetch_params: &mut FetchParams,
314    cache: &mut CorsCache,
315    recursive_flag: bool,
316    target: Target<'_>,
317    done_chan: &mut DoneChannel,
318    context: &FetchContext,
319) -> Response {
320    // Step 1: Let request be fetchParam's request.
321    let request = &mut fetch_params.request;
322    send_early_httprequest_to_devtools(request, context);
323    // Step 2: Let response be null.
324    let mut response = None;
325
326    // Servo internal: return a crash error when a crash error page is needed
327    if let Some(ref details) = request.crash {
328        response = Some(Response::network_error(NetworkError::Crash(
329            details.clone(),
330        )));
331    }
332
333    // Step 3: If request’s local-URLs-only flag is set and request’s
334    // current URL is not local, then set response to a network error.
335    if request.local_urls_only &&
336        !matches!(
337            request.current_url().scheme(),
338            "about" | "blob" | "data" | "filesystem"
339        )
340    {
341        response = Some(Response::network_error(NetworkError::Internal(
342            "Non-local scheme".into(),
343        )));
344    }
345
346    // The request should have a valid policy_container associated with it.
347    let policy_container = match &request.policy_container {
348        RequestPolicyContainer::Client => unreachable!(),
349        RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
350    };
351    let csp_request = convert_request_to_csp_request(request);
352    if let Some(csp_request) = csp_request.as_ref() {
353        // Step 2.2.
354        let violations = report_violations_for_request_by_csp(csp_request, &policy_container);
355
356        if !violations.is_empty() {
357            target.process_csp_violations(request, violations);
358        }
359    };
360
361    // Step 3.
362    // TODO: handle request abort.
363
364    // Step 4. Upgrade request to a potentially trustworthy URL, if appropriate.
365    if should_upgrade_request_to_potentially_trustworthy(request, context) ||
366        should_upgrade_mixed_content_request(request, &context.protocols)
367    {
368        trace!(
369            "upgrading {} targeting {:?}",
370            request.current_url(),
371            request.destination
372        );
373        if let Some(new_scheme) = match request.current_url().scheme() {
374            "http" => Some("https"),
375            "ws" => Some("wss"),
376            _ => None,
377        } {
378            request
379                .current_url_mut()
380                .as_mut_url()
381                .set_scheme(new_scheme)
382                .unwrap();
383        }
384    } else {
385        trace!(
386            "not upgrading {} targeting {:?} with {:?}",
387            request.current_url(),
388            request.destination,
389            request.insecure_requests_policy
390        );
391    }
392    if let Some(csp_request) = csp_request.as_ref() {
393        // Step 7. If should request be blocked due to a bad port, should fetching request be blocked
394        // as mixed content, or should request be blocked by Content Security Policy returns blocked,
395        // then set response to a network error.
396        let (check_result, violations) =
397            should_request_be_blocked_by_csp(csp_request, &policy_container);
398
399        if !violations.is_empty() {
400            target.process_csp_violations(request, violations);
401        }
402
403        if check_result == csp::CheckResult::Blocked {
404            warn!("Request blocked by CSP");
405            response = Some(Response::network_error(NetworkError::Internal(
406                "Blocked by Content-Security-Policy".into(),
407            )))
408        }
409    };
410    if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) {
411        response = Some(Response::network_error(NetworkError::Internal(
412            "Request attempted on bad port".into(),
413        )));
414    }
415    if should_request_be_blocked_as_mixed_content(request, &context.protocols) {
416        response = Some(Response::network_error(NetworkError::Internal(
417            "Blocked as mixed content".into(),
418        )));
419    }
420
421    // Step 8: If request’s referrer policy is the empty string, then set request’s referrer policy
422    // to request’s policy container’s referrer policy.
423    if request.referrer_policy == ReferrerPolicy::EmptyString {
424        request.referrer_policy = policy_container.get_referrer_policy();
425    }
426
427    let referrer_url = match mem::replace(&mut request.referrer, Referrer::NoReferrer) {
428        Referrer::NoReferrer => None,
429        Referrer::ReferrerUrl(referrer_source) | Referrer::Client(referrer_source) => {
430            request.headers.remove(header::REFERER);
431            determine_requests_referrer(
432                request.referrer_policy,
433                referrer_source,
434                request.current_url(),
435            )
436        },
437    };
438    request.referrer = referrer_url.map_or(Referrer::NoReferrer, Referrer::ReferrerUrl);
439
440    // Step 9.
441    // TODO: handle FTP URLs.
442
443    // Step 10.
444    context
445        .state
446        .hsts_list
447        .read()
448        .apply_hsts_rules(request.current_url_mut());
449
450    // Step 11.
451    // Not applicable: see fetch_async.
452
453    let current_url = request.current_url();
454    let current_scheme = current_url.scheme();
455
456    // Intercept the request and maybe override the response.
457    context
458        .request_interceptor
459        .lock()
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();
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    let http_cache = context.state.http_cache.write();
790    http_cache.update_awaiting_consumers(request, &response);
791
792    // Steps 25-27.
793    // TODO: remove this line when only asynchronous fetches are used
794    response
795}
796
797async fn wait_for_response(
798    request: &Request,
799    response: &mut Response,
800    target: Target<'_>,
801    done_chan: &mut DoneChannel,
802    context: &FetchContext,
803) {
804    if let Some(ref mut ch) = *done_chan {
805        let mut devtools_body = context.devtools_chan.as_ref().map(|_| Vec::new());
806        loop {
807            match ch.1.recv().await {
808                Some(Data::Payload(vec)) => {
809                    if let Some(body) = devtools_body.as_mut() {
810                        body.extend(&vec);
811                    }
812                    target.process_response_chunk(request, vec);
813                },
814                Some(Data::Done) => {
815                    send_response_to_devtools(request, context, response, devtools_body);
816                    break;
817                },
818                Some(Data::Cancelled) => {
819                    response.aborted.store(true, Ordering::Release);
820                    break;
821                },
822                _ => {
823                    panic!("fetch worker should always send Done before terminating");
824                },
825            }
826        }
827    } else {
828        match *response.actual_response().body.lock() {
829            ResponseBody::Done(ref vec) if !vec.is_empty() => {
830                // in case there was no channel to wait for, the body was
831                // obtained synchronously via scheme_fetch for data/file/about/etc
832                // We should still send the body across as a chunk
833                target.process_response_chunk(request, vec.clone());
834                if context.devtools_chan.is_some() {
835                    // Now that we've replayed the entire cached body,
836                    // notify the DevTools server with the full Response.
837                    send_response_to_devtools(request, context, response, Some(vec.clone()));
838                }
839            },
840            ResponseBody::Done(_) | ResponseBody::Empty => {},
841            _ => unreachable!(),
842        }
843    }
844}
845
846/// Range header start and end values.
847pub enum RangeRequestBounds {
848    /// The range bounds are known and set to final values.
849    Final(RelativePos),
850    /// We need extra information to set the range bounds.
851    /// i.e. buffer or file size.
852    Pending(u64),
853}
854
855impl RangeRequestBounds {
856    pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, &'static str> {
857        match self {
858            RangeRequestBounds::Final(pos) => {
859                if let Some(len) = len {
860                    if pos.start <= len as i64 {
861                        return Ok(*pos);
862                    }
863                }
864                Err("Tried to process RangeRequestBounds::Final without len")
865            },
866            RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
867                if let Some(len) = len {
868                    Some((len - u64::min(len, *offset)) as i64)
869                } else {
870                    Some(0)
871                },
872                None,
873            )),
874        }
875    }
876}
877
878fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
879    let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
880    response
881        .headers
882        .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
883    *response.body.lock() = ResponseBody::Done(vec![]);
884    response.status = HttpStatus::default();
885    response
886}
887
888fn create_about_memory(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
889    let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
890    response
891        .headers
892        .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
893    *response.body.lock() = ResponseBody::Done(resources::read_bytes(Resource::AboutMemoryHTML));
894    response.status = HttpStatus::default();
895    response
896}
897
898/// Handle a request from the user interface to ignore validation errors for a certificate.
899fn handle_allowcert_request(request: &mut Request, context: &FetchContext) -> io::Result<()> {
900    let error = |string| Err(io::Error::other(string));
901
902    let body = match request.body.as_mut() {
903        Some(body) => body,
904        None => return error("No body found"),
905    };
906
907    let stream = body.take_stream();
908    let stream = stream.lock();
909    let (body_chan, body_port) = ipc::channel().unwrap();
910    let _ = stream.send(BodyChunkRequest::Connect(body_chan));
911    let _ = stream.send(BodyChunkRequest::Chunk);
912    let body_bytes = match body_port.recv().ok() {
913        Some(BodyChunkResponse::Chunk(bytes)) => bytes,
914        _ => return error("Certificate not sent in a single chunk"),
915    };
916
917    let split_idx = match body_bytes.iter().position(|b| *b == b'&') {
918        Some(split_idx) => split_idx,
919        None => return error("Could not find ampersand in data"),
920    };
921    let (secret, cert_base64) = body_bytes.split_at(split_idx);
922
923    let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
924    if secret != Some(*net_traits::PRIVILEGED_SECRET) {
925        return error("Invalid secret sent. Ignoring request");
926    }
927
928    let cert_bytes = match general_purpose::STANDARD_NO_PAD.decode(&cert_base64[1..]) {
929        Ok(bytes) => bytes,
930        Err(_) => return error("Could not decode certificate base64"),
931    };
932
933    context
934        .state
935        .override_manager
936        .add_override(&CertificateDer::from_slice(&cert_bytes).into_owned());
937    Ok(())
938}
939
940/// [Scheme fetch](https://fetch.spec.whatwg.org#scheme-fetch)
941async fn scheme_fetch(
942    fetch_params: &mut FetchParams,
943    cache: &mut CorsCache,
944    target: Target<'_>,
945    done_chan: &mut DoneChannel,
946    context: &FetchContext,
947) -> Response {
948    // Step 1: If fetchParams is canceled, then return the appropriate network error for fetchParams.
949
950    // Step 2: Let request be fetchParams’s request.
951    let request = &mut fetch_params.request;
952    let url = request.current_url();
953
954    let scheme = url.scheme();
955    match scheme {
956        "about" if url.path() == "blank" => create_blank_reply(url, request.timing_type()),
957        "about" if url.path() == "memory" => create_about_memory(url, request.timing_type()),
958
959        "chrome" if url.path() == "allowcert" => {
960            if let Err(error) = handle_allowcert_request(request, context) {
961                warn!("Could not handle allowcert request: {error}");
962            }
963            create_blank_reply(url, request.timing_type())
964        },
965
966        "http" | "https" => {
967            http_fetch(
968                fetch_params,
969                cache,
970                false,
971                false,
972                false,
973                target,
974                done_chan,
975                context,
976            )
977            .await
978        },
979
980        _ => match context.protocols.get(scheme) {
981            Some(handler) => handler.load(request, done_chan, context).await,
982            None => Response::network_error(NetworkError::Internal("Unexpected scheme".into())),
983        },
984    }
985}
986
987fn is_null_body_status(status: &HttpStatus) -> bool {
988    matches!(
989        status.try_code(),
990        Some(StatusCode::SWITCHING_PROTOCOLS) |
991            Some(StatusCode::NO_CONTENT) |
992            Some(StatusCode::RESET_CONTENT) |
993            Some(StatusCode::NOT_MODIFIED)
994    )
995}
996
997/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-nosniff?>
998pub fn should_be_blocked_due_to_nosniff(
999    destination: Destination,
1000    response_headers: &HeaderMap,
1001) -> bool {
1002    // Step 1
1003    if !determine_nosniff(response_headers) {
1004        return false;
1005    }
1006
1007    // Step 2
1008    // Note: an invalid MIME type will produce a `None`.
1009    let mime_type = extract_mime_type_as_mime(response_headers);
1010
1011    /// <https://html.spec.whatwg.org/multipage/#scriptingLanguages>
1012    #[inline]
1013    fn is_javascript_mime_type(mime_type: &Mime) -> bool {
1014        let javascript_mime_types: [Mime; 16] = [
1015            "application/ecmascript".parse().unwrap(),
1016            "application/javascript".parse().unwrap(),
1017            "application/x-ecmascript".parse().unwrap(),
1018            "application/x-javascript".parse().unwrap(),
1019            "text/ecmascript".parse().unwrap(),
1020            "text/javascript".parse().unwrap(),
1021            "text/javascript1.0".parse().unwrap(),
1022            "text/javascript1.1".parse().unwrap(),
1023            "text/javascript1.2".parse().unwrap(),
1024            "text/javascript1.3".parse().unwrap(),
1025            "text/javascript1.4".parse().unwrap(),
1026            "text/javascript1.5".parse().unwrap(),
1027            "text/jscript".parse().unwrap(),
1028            "text/livescript".parse().unwrap(),
1029            "text/x-ecmascript".parse().unwrap(),
1030            "text/x-javascript".parse().unwrap(),
1031        ];
1032
1033        javascript_mime_types
1034            .iter()
1035            .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
1036    }
1037
1038    match mime_type {
1039        // Step 4
1040        Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type),
1041        // Step 5
1042        Some(ref mime_type) if destination == Destination::Style => {
1043            mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS
1044        },
1045
1046        None if destination == Destination::Style || destination.is_script_like() => true,
1047        // Step 6
1048        _ => false,
1049    }
1050}
1051
1052/// <https://fetch.spec.whatwg.org/#should-response-to-request-be-blocked-due-to-mime-type?>
1053fn should_be_blocked_due_to_mime_type(
1054    destination: Destination,
1055    response_headers: &HeaderMap,
1056) -> bool {
1057    // Step 1: Let mimeType be the result of extracting a MIME type from response’s header list.
1058    let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) {
1059        Some(mime_type) => mime_type,
1060        // Step 2: If mimeType is failure, then return allowed.
1061        None => return false,
1062    };
1063
1064    // Step 3: Let destination be request’s destination.
1065    // Step 4: If destination is script-like and one of the following is true, then return blocked:
1066    //    - mimeType’s essence starts with "audio/", "image/", or "video/".
1067    //    - mimeType’s essence is "text/csv".
1068    // Step 5: Return allowed.
1069    destination.is_script_like() &&
1070        match mime_type.type_() {
1071            mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
1072            mime::TEXT if mime_type.subtype() == mime::CSV => true,
1073            _ => false,
1074        }
1075}
1076
1077/// <https://fetch.spec.whatwg.org/#block-bad-port>
1078pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
1079    // Step 1. Let url be request’s current URL.
1080    // NOTE: We receive the request url as an argument
1081
1082    // Step 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port, then return blocked.
1083    let is_http_scheme = matches!(url.scheme(), "http" | "https");
1084    let is_bad_port = url.port().is_some_and(is_bad_port);
1085    if is_http_scheme && is_bad_port {
1086        return true;
1087    }
1088
1089    // Step 3. Return allowed.
1090    false
1091}
1092
1093/// <https://w3c.github.io/webappsec-mixed-content/#should-block-fetch>
1094pub fn should_request_be_blocked_as_mixed_content(
1095    request: &Request,
1096    protocol_registry: &ProtocolRegistry,
1097) -> bool {
1098    // Step 1. Return allowed if one or more of the following conditions are met:
1099    // 1.1. Does settings prohibit mixed security contexts?
1100    // returns "Does Not Restrict Mixed Security Contexts" when applied to request’s client.
1101    if do_settings_prohibit_mixed_security_contexts(request) ==
1102        MixedSecurityProhibited::NotProhibited
1103    {
1104        return false;
1105    }
1106
1107    // 1.2. request’s URL is a potentially trustworthy URL.
1108    if is_url_potentially_trustworthy(protocol_registry, &request.url()) {
1109        return false;
1110    }
1111
1112    // 1.3. The user agent has been instructed to allow mixed content.
1113
1114    // 1.4. request’s destination is "document", and request’s target browsing context has
1115    // no parent browsing context.
1116    if request.destination == Destination::Document {
1117        // TODO: request's target browsing context has no parent browsing context
1118        return false;
1119    }
1120
1121    true
1122}
1123
1124/// <https://w3c.github.io/webappsec-mixed-content/#should-block-response>
1125pub fn should_response_be_blocked_as_mixed_content(
1126    request: &Request,
1127    response: &Response,
1128    protocol_registry: &ProtocolRegistry,
1129) -> bool {
1130    // Step 1. Return allowed if one or more of the following conditions are met:
1131    // 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content
1132    // when applied to request’s client.
1133    if do_settings_prohibit_mixed_security_contexts(request) ==
1134        MixedSecurityProhibited::NotProhibited
1135    {
1136        return false;
1137    }
1138
1139    // 1.2. response’s url is a potentially trustworthy URL.
1140    if response
1141        .actual_response()
1142        .url()
1143        .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
1144    {
1145        return false;
1146    }
1147
1148    // 1.3. TODO: The user agent has been instructed to allow mixed content.
1149
1150    // 1.4. request’s destination is "document", and request’s target browsing context
1151    // has no parent browsing context.
1152    if request.destination == Destination::Document {
1153        // TODO: if requests target browsing context has no parent browsing context
1154        return false;
1155    }
1156
1157    true
1158}
1159
1160/// <https://fetch.spec.whatwg.org/#bad-port>
1161fn is_bad_port(port: u16) -> bool {
1162    static BAD_PORTS: [u16; 78] = [
1163        1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
1164        102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
1165        427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
1166        993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
1167        6668, 6669, 6697, 10080,
1168    ];
1169
1170    BAD_PORTS.binary_search(&port).is_ok()
1171}
1172
1173// TODO : Investigate and need to revisit again
1174pub fn is_form_submission_request(request: &Request) -> bool {
1175    let content_type = request.headers.typed_get::<ContentType>();
1176    content_type.is_some_and(|ct| {
1177        let mime: Mime = ct.into();
1178        mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED
1179    })
1180}
1181
1182/// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request>
1183fn should_upgrade_request_to_potentially_trustworthy(
1184    request: &mut Request,
1185    context: &FetchContext,
1186) -> bool {
1187    fn should_upgrade_navigation_request(request: &Request) -> bool {
1188        // Step 2.1 If request is a form submission, skip the remaining substeps, and continue upgrading request.
1189        if is_form_submission_request(request) {
1190            return true;
1191        }
1192
1193        // Step 2.2
1194        // TODO If request’s client's target browsing context is a nested browsing context
1195
1196        // Step 2.4
1197        // TODO : check for insecure navigation set after its implemention
1198
1199        // Step 2.5 Return without further modifying request
1200        false
1201    }
1202
1203    // Step 1. If request is a navigation request,
1204    if request.is_navigation_request() {
1205        // Append a header named Upgrade-Insecure-Requests with a value of 1 to
1206        // request’s header list if any of the following criteria are met:
1207        // * request’s URL is not a potentially trustworthy URL
1208        // * request’s URL's host is not a preloadable HSTS host
1209        if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
1210            request
1211                .current_url()
1212                .host_str()
1213                .is_none_or(|host| context.state.hsts_list.read().is_host_secure(host))
1214        {
1215            debug!("Appending the Upgrade-Insecure-Requests header to request’s header list");
1216            request
1217                .headers
1218                .insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1"));
1219        }
1220
1221        if !should_upgrade_navigation_request(request) {
1222            return false;
1223        }
1224    }
1225
1226    // Step 4
1227    request.insecure_requests_policy == InsecureRequestsPolicy::Upgrade
1228}
1229
1230#[derive(Debug, PartialEq)]
1231pub enum MixedSecurityProhibited {
1232    Prohibited,
1233    NotProhibited,
1234}
1235
1236/// <https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object>
1237fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
1238    if let Origin::Origin(ref origin) = request.origin {
1239        // Workers created from a data: url are secure if they were created from secure contexts
1240        let is_origin_data_url_worker = matches!(
1241            *origin,
1242            ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
1243        );
1244
1245        // Step 1. If settings’ origin is a potentially trustworthy origin,
1246        // then return "Prohibits Mixed Security Contexts".
1247        if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
1248            return MixedSecurityProhibited::Prohibited;
1249        }
1250    }
1251
1252    // Step 2.2. For each navigable navigable in document’s ancestor navigables:
1253    // Step 2.2.1. If navigable’s active document's origin is a potentially trustworthy origin,
1254    // then return "Prohibits Mixed Security Contexts".
1255    if request.has_trustworthy_ancestor_origin {
1256        return MixedSecurityProhibited::Prohibited;
1257    }
1258
1259    MixedSecurityProhibited::NotProhibited
1260}
1261
1262/// <https://w3c.github.io/webappsec-mixed-content/#upgrade-algorithm>
1263fn should_upgrade_mixed_content_request(
1264    request: &Request,
1265    protocol_registry: &ProtocolRegistry,
1266) -> bool {
1267    let url = request.url();
1268    // Step 1.1 : request’s URL is a potentially trustworthy URL.
1269    if is_url_potentially_trustworthy(protocol_registry, &url) {
1270        return false;
1271    }
1272
1273    // Step 1.2 : request’s URL’s host is an IP address.
1274    match url.host() {
1275        Some(Host::Ipv4(_)) | Some(Host::Ipv6(_)) => return false,
1276        _ => (),
1277    }
1278
1279    // Step 1.3
1280    if do_settings_prohibit_mixed_security_contexts(request) ==
1281        MixedSecurityProhibited::NotProhibited
1282    {
1283        return false;
1284    }
1285
1286    // Step 1.4 : request’s destination is not "image", "audio", or "video".
1287    if !matches!(
1288        request.destination,
1289        Destination::Audio | Destination::Image | Destination::Video
1290    ) {
1291        return false;
1292    }
1293
1294    // Step 1.5 : request’s destination is "image" and request’s initiator is "imageset".
1295    if request.destination == Destination::Image && request.initiator == Initiator::ImageSet {
1296        return false;
1297    }
1298
1299    true
1300}