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