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(
29 &self,
30 request: &mut Request,
31 done_chan: &mut DoneChannel,
32 context: &FetchContext,
33 ) -> Pin<Box<dyn Future<Output = Response> + Send>> {
34 let url = request.current_url();
35
36 if request.method != Method::GET {
37 return Box::pin(ready(Response::network_error(NetworkError::Internal(
38 "Unexpected method for file".into(),
39 ))));
40 }
41 let response = if let Ok(file_path) = url.to_file_path() {
42 if file_path.is_dir() {
43 return Box::pin(ready(local_directory_listing::fetch(
44 request, url, file_path,
45 )));
46 }
47
48 if let Ok(file) = File::open(file_path.clone()) {
49 let file_size = match file.metadata() {
52 Ok(metadata) => Some(metadata.len()),
53 Err(_) => None,
54 };
55
56 let mut response =
57 Response::new(url, ResourceFetchTiming::new(request.timing_type()));
58
59 let range_header = request.headers.typed_get::<Range>();
60 let is_range_request = range_header.is_some();
61 let Ok(range) = get_range_request_bounds(range_header, file_size.unwrap_or(0))
62 .get_final(file_size)
63 else {
64 range_not_satisfiable_error(&mut response);
65 return Box::pin(ready(response));
66 };
67 let mut reader = BufReader::with_capacity(FILE_CHUNK_SIZE, file);
68 if reader.seek(SeekFrom::Start(range.start as u64)).is_err() {
69 return Box::pin(ready(Response::network_error(NetworkError::Internal(
70 "Unexpected method for file".into(),
71 ))));
72 }
73
74 if is_range_request {
77 partial_content(&mut response);
78 }
79
80 let mime = mime_guess::from_path(file_path).first_or_octet_stream();
82 response.headers.typed_insert(ContentType::from(mime));
83
84 let (mut done_sender, done_receiver) = unbounded_channel();
87 *done_chan = Some((done_sender.clone(), done_receiver));
88
89 *response.body.lock().unwrap() = ResponseBody::Receiving(vec![]);
90
91 context.filemanager.lock().unwrap().fetch_file_in_chunks(
92 &mut done_sender,
93 reader,
94 response.body.clone(),
95 context.cancellation_listener.clone(),
96 range,
97 );
98
99 response
100 } else {
101 Response::network_error(NetworkError::Internal("Opening file failed".into()))
102 }
103 } else {
104 Response::network_error(NetworkError::Internal(
105 "Constructing file path failed".into(),
106 ))
107 };
108
109 Box::pin(ready(response))
110 }
111}