net/fetch/
methods.rs

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