Rewild Your Web
web
browser
dweb
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::ancestor_dir_path("resources")
47 .join("resource_protocol")
48 .join(path);
49
50 if !file_path.exists() || file_path.is_dir() {
51 return Box::pin(std::future::ready(Response::network_error(
52 NetworkError::ResourceLoadError("Invalid path".to_owned()),
53 )));
54 }
55
56 let response = if let Ok(file) = File::open(file_path.clone()) {
57 let mut response = Response::new(
58 request.current_url(),
59 ResourceFetchTiming::new(request.timing_type()),
60 );
61 let reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
62
63 // Set Content-Type header.
64 let mime = mime_guess::from_path(file_path).first_or_octet_stream();
65 response.headers.typed_insert(ContentType::from(mime));
66
67 // Setup channel to receive cross-thread messages about the file fetch
68 // operation.
69 let (mut done_sender, done_receiver) = unbounded_channel();
70 *done_chan = Some((done_sender.clone(), done_receiver));
71
72 *response.body.lock() = ResponseBody::Receiving(vec![]);
73
74 context.filemanager.fetch_file_in_chunks(
75 &mut done_sender,
76 reader,
77 response.body.clone(),
78 context.cancellation_listener.clone(),
79 RelativePos::full_range(),
80 );
81
82 response
83 } else {
84 Response::network_error(NetworkError::ResourceLoadError(
85 "Opening file failed".to_owned(),
86 ))
87 };
88
89 Box::pin(std::future::ready(response))
90 }
91}
92
93impl ProtocolHandler for ResourceProtocolHandler {
94 fn load(
95 &self,
96 request: &mut Request,
97 done_chan: &mut DoneChannel,
98 context: &FetchContext,
99 ) -> Pin<Box<dyn Future<Output = Response> + Send>> {
100 let url = request.current_url();
101
102 // TODO: Check referrer.
103 // We unexpectedly get `NoReferrer` for all requests from the newtab page.
104
105 Self::response_for_path(request, done_chan, context, url.path())
106 }
107}