aboutsummaryrefslogtreecommitdiffstats
path: root/components/net/protocols/file.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/net/protocols/file.rs')
-rw-r--r--components/net/protocols/file.rs109
1 files changed, 109 insertions, 0 deletions
diff --git a/components/net/protocols/file.rs b/components/net/protocols/file.rs
new file mode 100644
index 00000000000..65e134e4635
--- /dev/null
+++ b/components/net/protocols/file.rs
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::fs::File;
+use std::future::{ready, Future};
+use std::io::{BufReader, Seek, SeekFrom};
+use std::pin::Pin;
+
+use headers::{ContentType, HeaderMapExt, Range};
+use http::Method;
+use net_traits::request::Request;
+use net_traits::response::{Response, ResponseBody};
+use net_traits::{NetworkError, ResourceFetchTiming};
+use tokio::sync::mpsc::unbounded_channel;
+
+use crate::fetch::methods::{DoneChannel, FetchContext};
+use crate::filemanager_thread::FILE_CHUNK_SIZE;
+use crate::local_directory_listing;
+use crate::protocols::{
+ get_range_request_bounds, partial_content, range_not_satisfiable_error, ProtocolHandler,
+};
+
+#[derive(Default)]
+pub struct FileProtocolHander {}
+
+impl ProtocolHandler for FileProtocolHander {
+ fn load(
+ &self,
+ request: &mut Request,
+ done_chan: &mut DoneChannel,
+ context: &FetchContext,
+ ) -> Pin<Box<dyn Future<Output = Response> + Send>> {
+ let url = request.current_url();
+
+ if request.method != Method::GET {
+ return Box::pin(ready(Response::network_error(NetworkError::Internal(
+ "Unexpected method for file".into(),
+ ))));
+ }
+ let response = if let Ok(file_path) = url.to_file_path() {
+ if file_path.is_dir() {
+ return Box::pin(ready(local_directory_listing::fetch(
+ request, url, file_path,
+ )));
+ }
+
+ if let Ok(file) = File::open(file_path.clone()) {
+ // Get range bounds (if any) and try to seek to the requested offset.
+ // If seeking fails, bail out with a NetworkError.
+ let file_size = match file.metadata() {
+ Ok(metadata) => Some(metadata.len()),
+ Err(_) => None,
+ };
+
+ let mut response =
+ Response::new(url, ResourceFetchTiming::new(request.timing_type()));
+
+ let range_header = request.headers.typed_get::<Range>();
+ let is_range_request = range_header.is_some();
+ let Ok(range) = get_range_request_bounds(range_header).get_final(file_size) else {
+ range_not_satisfiable_error(&mut response);
+ return Box::pin(ready(response));
+ };
+ let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
+ if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
+ return Box::pin(ready(Response::network_error(NetworkError::Internal(
+ "Unexpected method for file".into(),
+ ))));
+ }
+
+ // Set response status to 206 if Range header is present.
+ // At this point we should have already validated the header.
+ if is_range_request {
+ partial_content(&mut response);
+ }
+
+ // Set Content-Type header.
+ let mime = mime_guess::from_path(file_path).first_or_octet_stream();
+ response.headers.typed_insert(ContentType::from(mime));
+
+ // Setup channel to receive cross-thread messages about the file fetch
+ // operation.
+ let (mut done_sender, done_receiver) = unbounded_channel();
+ *done_chan = Some((done_sender.clone(), done_receiver));
+
+ *response.body.lock().unwrap() = ResponseBody::Receiving(vec![]);
+
+ context.filemanager.lock().unwrap().fetch_file_in_chunks(
+ &mut done_sender,
+ reader,
+ response.body.clone(),
+ context.cancellation_listener.clone(),
+ range,
+ );
+
+ response
+ } else {
+ Response::network_error(NetworkError::Internal("Opening file failed".into()))
+ }
+ } else {
+ Response::network_error(NetworkError::Internal(
+ "Constructing file path failed".into(),
+ ))
+ };
+
+ Box::pin(ready(response))
+ }
+}