servoshell/desktop/protocols/
resource.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
5//! This protocol handler loads files from the <resources_dir_path()>/protocol/resource directory,
6//! sanitizing the path to prevent path escape attacks.
7//! For security reasons, loads are only allowed if the referrer has a 'resource' or
8//! 'servo' scheme.
9
10use std::fs::File;
11use std::future::Future;
12use std::io::BufReader;
13use std::pin::Pin;
14
15use headers::{ContentType, HeaderMapExt};
16use net::fetch::methods::{DoneChannel, FetchContext};
17use net::filemanager_thread::FILE_CHUNK_SIZE;
18use net::protocols::ProtocolHandler;
19use net_traits::ResourceFetchTiming;
20use net_traits::filemanager_thread::RelativePos;
21use net_traits::request::Request;
22use net_traits::response::{Response, ResponseBody};
23use tokio::sync::mpsc::unbounded_channel;
24
25#[derive(Default)]
26pub struct ResourceProtocolHandler {}
27
28impl ResourceProtocolHandler {
29    pub fn response_for_path(
30        request: &mut Request,
31        done_chan: &mut DoneChannel,
32        context: &FetchContext,
33        path: &str,
34    ) -> Pin<Box<dyn Future<Output = Response> + Send>> {
35        if path.contains("..") || !path.starts_with("/") {
36            return Box::pin(std::future::ready(Response::network_internal_error(
37                "Invalid path",
38            )));
39        }
40
41        let path = if let Some(path) = path.strip_prefix("/") {
42            path
43        } else {
44            return Box::pin(std::future::ready(Response::network_internal_error(
45                "Invalid path",
46            )));
47        };
48
49        let file_path = crate::resources::resources_dir_path()
50            .join("resource_protocol")
51            .join(path);
52
53        if !file_path.exists() || file_path.is_dir() {
54            return Box::pin(std::future::ready(Response::network_internal_error(
55                "Invalid path",
56            )));
57        }
58
59        let response = if let Ok(file) = File::open(file_path.clone()) {
60            let mut response = Response::new(
61                request.current_url(),
62                ResourceFetchTiming::new(request.timing_type()),
63            );
64            let reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
65
66            // Set Content-Type header.
67            let mime = mime_guess::from_path(file_path).first_or_octet_stream();
68            response.headers.typed_insert(ContentType::from(mime));
69
70            // Setup channel to receive cross-thread messages about the file fetch
71            // operation.
72            let (mut done_sender, done_receiver) = unbounded_channel();
73            *done_chan = Some((done_sender.clone(), done_receiver));
74
75            *response.body.lock().unwrap() = ResponseBody::Receiving(vec![]);
76
77            context.filemanager.lock().unwrap().fetch_file_in_chunks(
78                &mut done_sender,
79                reader,
80                response.body.clone(),
81                context.cancellation_listener.clone(),
82                RelativePos::full_range(),
83            );
84
85            response
86        } else {
87            Response::network_internal_error("Opening file failed")
88        };
89
90        Box::pin(std::future::ready(response))
91    }
92}
93
94impl ProtocolHandler for ResourceProtocolHandler {
95    fn load(
96        &self,
97        request: &mut Request,
98        done_chan: &mut DoneChannel,
99        context: &FetchContext,
100    ) -> Pin<Box<dyn Future<Output = Response> + Send>> {
101        let url = request.current_url();
102
103        // TODO: Check referrer.
104        //       We unexpectedly get `NoReferrer` for all requests from the newtab page.
105
106        Self::response_for_path(request, done_chan, context, url.path())
107    }
108}