1use std::fs::File;
6use std::future::{Future, ready};
7use std::io::{BufReader, Seek, SeekFrom};
8use std::pin::Pin;
9
10use headers::{ContentLength, ContentRange, 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 end_byte = range.end.map(|e| e as u64).or(file_size);
78 if let Some(end_byte) = end_byte {
79 let start_byte = range.start as u64;
80
81 response
82 .headers
83 .typed_insert(ContentLength(end_byte - start_byte));
84 if is_range_request &&
85 let Ok(content_range) =
86 ContentRange::bytes(start_byte..end_byte, file_size)
87 {
88 response.headers.typed_insert(content_range);
89 }
90 }
91
92 let mime = mime_guess::from_path(file_path).first_or_octet_stream();
94 response.headers.typed_insert(ContentType::from(mime));
95
96 let (mut done_sender, done_receiver) = unbounded_channel();
99 *done_chan = Some((done_sender.clone(), done_receiver));
100
101 *response.body.lock() = ResponseBody::Receiving(vec![]);
102
103 context.filemanager.fetch_file_in_chunks(
104 &mut done_sender,
105 reader,
106 response.body.clone(),
107 context.cancellation_listener.clone(),
108 range,
109 );
110
111 response
112 } else {
113 Response::network_error(NetworkError::ResourceLoadError(
114 "Opening file failed".into(),
115 ))
116 }
117 } else {
118 Response::network_error(NetworkError::ResourceLoadError(
119 "Constructing file path failed".into(),
120 ))
121 };
122
123 Box::pin(ready(response))
124 }
125}