1use std::fs::File;
6use std::future::{Future, ready};
7use std::io::{BufReader, Seek, SeekFrom};
8use std::pin::Pin;
9
10use headers::{ContentType, HeaderMapExt, Range};
11use http::Method;
12use net_traits::request::Request;
13use net_traits::response::{Response, ResponseBody};
14use net_traits::{NetworkError, ResourceFetchTiming};
15use tokio::sync::mpsc::unbounded_channel;
16
17use crate::fetch::methods::{DoneChannel, FetchContext};
18use crate::filemanager_thread::FILE_CHUNK_SIZE;
19use crate::local_directory_listing;
20use crate::protocols::{
21 ProtocolHandler, get_range_request_bounds, partial_content, range_not_satisfiable_error,
22};
23
24#[derive(Default)]
25pub struct FileProtocolHander {}
26
27impl ProtocolHandler for FileProtocolHander {
28 fn load<'a>(
29 &'a self,
30 request: &'a mut Request,
31 done_chan: &mut DoneChannel,
32 context: &FetchContext,
33 ) -> Pin<Box<dyn Future<Output = Response> + Send + 'a>> {
34 let url = request.current_url();
35
36 if request.method != Method::GET {
37 return Box::pin(ready(Response::network_error(NetworkError::InvalidMethod)));
38 }
39 let response = if let Ok(file_path) = url.to_file_path() {
40 if file_path.is_dir() {
41 return Box::pin(async move {
42 local_directory_listing::fetch(request, url, file_path).await
43 });
44 }
45
46 if let Ok(file) = File::open(file_path.clone()) {
47 let file_size = match file.metadata() {
50 Ok(metadata) => Some(metadata.len()),
51 Err(_) => None,
52 };
53
54 let mut response =
55 Response::new(url, ResourceFetchTiming::new(request.timing_type()));
56
57 let range_header = request.headers.typed_get::<Range>();
58 let is_range_request = range_header.is_some();
59 let Ok(range) = get_range_request_bounds(range_header, file_size.unwrap_or(0))
60 .get_final(file_size)
61 else {
62 range_not_satisfiable_error(&mut response);
63 return Box::pin(ready(response));
64 };
65 let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
66 if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
67 return Box::pin(ready(Response::network_error(NetworkError::InvalidMethod)));
68 }
69
70 if is_range_request {
73 partial_content(&mut response);
74 }
75
76 let mime = mime_guess::from_path(file_path).first_or_octet_stream();
78 response.headers.typed_insert(ContentType::from(mime));
79
80 let (mut done_sender, done_receiver) = unbounded_channel();
83 *done_chan = Some((done_sender.clone(), done_receiver));
84
85 *response.body.lock() = ResponseBody::Receiving(vec![]);
86
87 context.filemanager.fetch_file_in_chunks(
88 &mut done_sender,
89 reader,
90 response.body.clone(),
91 context.cancellation_listener.clone(),
92 range,
93 );
94
95 response
96 } else {
97 Response::network_error(NetworkError::ResourceLoadError(
98 "Opening file failed".into(),
99 ))
100 }
101 } else {
102 Response::network_error(NetworkError::ResourceLoadError(
103 "Constructing file path failed".into(),
104 ))
105 };
106
107 Box::pin(ready(response))
108 }
109}