1use percent_encoding::utf8_percent_encode;
4use crate::utils::PATH_ENCODE_SET;
5
6use std::fs;
7use std::path::Path;
8use core::marker;
9use core::fmt::{self, Write};
10use core::future::{self, Future};
11
12use crate::headers::cd::DispositionType;
13use crate::file::FileReadResult;
14
15pub const DEFAULT_SERVE_DIR: &'static str = ".";
17
18pub trait FsTaskSpawner: marker::Send + Clone + Unpin {
20 type SpawnError: std::error::Error + fmt::Debug + fmt::Display + Send + Sync + 'static;
22 type FileReadFut: Future<Output = Result<FileReadResult, Self::SpawnError>> + Unpin;
24
25 fn spawn_file_read<F: FnOnce() -> FileReadResult + Send + 'static>(fut: F) -> Self::FileReadFut;
27}
28
29pub trait FileServeConfig: marker::Send + Unpin {
31 fn max_buffer_size() -> u64 {
35 65_536
36 }
37
38 fn content_disposition_map(typ: mime::Name) -> DispositionType {
43 match typ {
44 mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
45 _ => DispositionType::Attachment,
46 }
47 }
48
49 fn is_use_etag(_path: &Path) -> bool {
55 true
56 }
57
58 fn is_use_last_modifier(_path: &Path) -> bool {
64 true
65 }
66}
67
68pub trait DirectoryListingConfig {
70 fn create_body(base: &Path, path: &Path, dir: fs::ReadDir) -> bytes::Bytes {
78 let mut body = crate::utils::BytesWriter::with_capacity(256);
79
80 let _ = write!(body, "<html>\n");
81 let _ = write!(body, " <head>\n");
82 let _ = write!(body, " <title>Index of {}/</title>\n", path.display());
83 let _ = write!(body, " </head>\n");
84 let _ = write!(body, " <body>\n");
85 let _ = write!(body, " <h1>Index of {}/</h1>\n", path.display());
86 let _ = write!(body, " <ul>\n");
87
88 let _ = write!(body, "<li><a href=\"/{}\">.</a></li>\n", utf8_percent_encode(&path.to_string_lossy(), PATH_ENCODE_SET));
89 if let Some(parent) = path.parent() {
90 let _ = write!(body, "<li><a href=\"/{}\">..</a></li>\n", utf8_percent_encode(&parent.to_string_lossy(), PATH_ENCODE_SET));
91 }
92
93 for entry in dir.filter_map(|entry| entry.ok()) {
94 if entry.file_name().to_str().map(|entry| entry.starts_with('.')).unwrap_or(false) {
95 continue;
96 }
97
98 let is_dir = match entry.metadata().map(|meta| meta.file_type()) {
99 Ok(meta) => if !meta.is_dir() && !meta.is_file() && !meta.is_symlink() {
100 continue;
101 } else {
102 meta.is_dir()
103 },
104 Err(_) => continue,
105 };
106
107 let entry_path = entry.path();
108 let entry_path = match entry_path.strip_prefix(base) {
109 Ok(res) => res,
110 Err(_) => &entry_path,
111 };
112
113 let _ = write!(body, "<li><a href=\"");
114 for component in entry_path.components().map(|component| component.as_os_str()) {
115 let _ = write!(body, "/{}", utf8_percent_encode(&component.to_string_lossy(), PATH_ENCODE_SET));
116 }
117
118 let _ = write!(body, "\">{}", v_htmlescape::escape(&entry.file_name().to_string_lossy()));
119
120 if is_dir {
121 let _ = write!(body, "/");
122 }
123
124 let _ = write!(body, "</a></li>\n");
125 }
126
127 let _ = write!(body, " </ul>\n");
128 let _ = write!(body, " </body>\n");
129 let _ = write!(body, "</html>\n");
130
131 body.freeze()
132 }
133}
134
135pub trait StaticFileConfig {
137 type FileService: FileServeConfig + 'static;
139 type DirService: DirectoryListingConfig;
141
142 fn is_method_allowed(method: &http::Method) -> bool {
145 match method {
146 &http::Method::GET | &http::Method::HEAD => true,
147 _ => false,
148 }
149 }
150
151 fn serve_dir(&self) -> &Path {
155 Path::new(DEFAULT_SERVE_DIR)
156 }
157
158 fn router_prefix(&self) -> &str {
164 "/"
165 }
166
167 fn index_file(&self, _path: &Path) -> Option<&Path> {
173 None
174 }
175
176 fn handle_directory(&self, _path: &Path) -> bool {
182 false
183 }
184
185 fn handle_not_found(&self, _path: &Path, _out_headers: &mut http::HeaderMap) -> (http::StatusCode, bytes::Bytes) {
191 (http::StatusCode::NOT_FOUND, bytes::Bytes::new())
192 }
193}
194
195#[derive(Clone, Copy)]
196pub struct DefaultConfig;
198impl FileServeConfig for DefaultConfig {}
199impl DirectoryListingConfig for DefaultConfig {}
200impl StaticFileConfig for DefaultConfig {
201 type FileService = Self;
202 type DirService = Self;
203}
204
205#[derive(Clone, Copy)]
206pub struct DummyWorker;
208impl FsTaskSpawner for DummyWorker {
209 type SpawnError = core::convert::Infallible;
210 type FileReadFut = future::Ready<Result<FileReadResult, Self::SpawnError>>;
211
212 #[cold]
213 #[inline(never)]
214 fn spawn_file_read<F: FnOnce() -> FileReadResult + Send + 'static>(_: F) -> Self::FileReadFut {
215 unimplemented!()
216 }
217}
218
219#[cfg(feature = "tokio")]
220#[derive(Clone, Copy)]
221pub struct TokioWorker;
225
226#[cfg(feature = "tokio")]
227impl FsTaskSpawner for TokioWorker {
228 type SpawnError = tokio::task::JoinError;
229 type FileReadFut = tokio::task::JoinHandle<FileReadResult>;
230
231 fn spawn_file_read<F: FnOnce() -> FileReadResult + Send + 'static>(fut: F) -> Self::FileReadFut {
232 tokio::task::spawn_blocking(fut)
233 }
234}