Skip to main content

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