trillium_static/
handler.rs1use crate::{
2 StaticConnExt,
3 fs_shims::{File, fs},
4 options::StaticOptions,
5};
6use relative_path::RelativePath;
7use std::path::{Path, PathBuf};
8use trillium::{Conn, Handler, conn_unwrap};
9
10#[derive(Debug)]
12pub struct StaticFileHandler {
13 fs_root: PathBuf,
14 index_file: Option<String>,
15 root_is_file: bool,
16 options: StaticOptions,
17}
18
19#[derive(Debug)]
20enum Record {
21 File(PathBuf, File),
22 Dir(PathBuf),
23}
24
25impl StaticFileHandler {
26 async fn resolve_fs_path(&self, url_path: &str) -> Option<PathBuf> {
27 let mut file_path = self.fs_root.clone();
28 log::trace!(
29 "attempting to resolve {} relative to {}",
30 url_path,
31 file_path.to_str().unwrap()
32 );
33 for segment in RelativePath::new(url_path) {
34 match segment {
35 "." => {}
36 ".." => {
37 file_path.pop();
38 }
39 _ => {
40 file_path.push(segment);
41 }
42 };
43 }
44
45 if file_path.starts_with(&self.fs_root) {
46 let path_buf = fs::canonicalize(file_path).await.ok();
47
48 #[cfg(feature = "async-std")]
49 return path_buf.map(Into::into);
50 #[cfg(not(feature = "async-std"))]
51 path_buf
52 } else {
53 None
54 }
55 }
56
57 async fn resolve(&self, url_path: &str) -> Option<Record> {
58 let fs_path = self.resolve_fs_path(url_path).await?;
59 let metadata = fs::metadata(&fs_path).await.ok()?;
60 if metadata.is_dir() {
61 log::trace!("resolved {} as dir {}", url_path, fs_path.to_str().unwrap());
62 Some(Record::Dir(fs_path))
63 } else if metadata.is_file() {
64 File::open(&fs_path)
65 .await
66 .ok()
67 .map(|file| Record::File(fs_path, file))
68 } else {
69 None
70 }
71 }
72
73 pub fn new(fs_root: impl AsRef<Path>) -> Self {
98 let fs_root = fs_root.as_ref().canonicalize().unwrap();
99 Self {
100 fs_root,
101 index_file: None,
102 root_is_file: false,
103 options: StaticOptions::default(),
104 }
105 }
106
107 pub fn without_etag_header(mut self) -> Self {
109 self.options.etag = false;
110 self
111 }
112
113 pub fn without_modified_header(mut self) -> Self {
115 self.options.modified = false;
116 self
117 }
118
119 pub fn with_index_file(mut self, file: &str) -> Self {
141 self.index_file = Some(file.to_string());
142 self
143 }
144}
145
146impl Handler for StaticFileHandler {
147 async fn init(&mut self, _info: &mut trillium::Info) {
148 self.root_is_file = match self.resolve("/").await {
149 Some(Record::File(path, _)) => {
150 log::info!("serving {:?} for all paths", path);
151 true
152 }
153
154 Some(Record::Dir(dir)) => {
155 log::info!("serving files within {:?}", dir);
156 false
157 }
158
159 None => {
160 log::error!(
161 "could not find {:?} on init, continuing anyway",
162 self.fs_root
163 );
164 false
165 }
166 };
167 }
168
169 async fn run(&self, conn: Conn) -> Conn {
170 match self.resolve(conn.path()).await {
171 Some(Record::File(path, file)) => conn.send_file(file).await.with_mime_from_path(path),
172
173 Some(Record::Dir(path)) => {
174 let index = conn_unwrap!(self.index_file.as_ref(), conn);
175 let path = path.join(index);
176 let file = conn_unwrap!(File::open(path.to_str().unwrap()).await.ok(), conn);
177 conn.send_file_with_options(file, &self.options)
178 .await
179 .with_mime_from_path(path)
180 }
181
182 _ => conn,
183 }
184 }
185}