rocket/fs/
rewrite.rs
1use std::borrow::Cow;
2use std::path::{Path, PathBuf};
3
4use crate::Request;
5use crate::http::{ext::IntoOwned, HeaderMap};
6use crate::response::Redirect;
7
8pub trait Rewriter: Send + Sync + 'static {
23 fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, req: &'r Request<'_>) -> Option<Rewrite<'r>>;
25}
26
27#[derive(Debug, Clone)]
29#[non_exhaustive]
30pub enum Rewrite<'r> {
31 File(File<'r>),
33 Redirect(Redirect),
35}
36
37#[derive(Debug, Clone)]
39#[non_exhaustive]
40pub struct File<'r> {
41 pub path: Cow<'r, Path>,
43 pub headers: HeaderMap<'r>,
45}
46
47impl<'r> File<'r> {
48 pub fn new(path: impl Into<Cow<'r, Path>>) -> Self {
50 Self { path: path.into(), headers: HeaderMap::new() }
51 }
52
53 pub fn checked<P: AsRef<Path>>(path: P) -> Self {
59 let path = path.as_ref();
60 if !path.exists() {
61 let path = path.display();
62 error!(%path, "FileServer path does not exist.\n\
63 Panicking to prevent inevitable handler error.");
64 panic!("missing file {}: refusing to continue", path);
65 }
66
67 Self::new(path.to_path_buf())
68 }
69
70 pub fn map_path<F, P>(self, f: F) -> Self
72 where F: FnOnce(Cow<'r, Path>) -> P, P: Into<Cow<'r, Path>>,
73 {
74 Self {
75 path: f(self.path).into(),
76 headers: self.headers,
77 }
78 }
79
80 pub fn is_hidden(&self) -> bool {
89 self.path.iter().any(|s| s.as_encoded_bytes().starts_with(b"."))
90 }
91
92 pub fn is_visible(&self) -> bool {
95 !self.is_hidden()
96 }
97}
98
99pub struct Prefix(PathBuf);
112
113impl Prefix {
114 pub fn checked<P: AsRef<Path>>(path: P) -> Self {
116 let path = path.as_ref();
117 if !path.is_dir() {
118 let path = path.display();
119 error!(%path, "FileServer path is not a directory.");
120 warn!("Aborting early to prevent inevitable handler error.");
121 panic!("invalid directory: refusing to continue");
122 }
123
124 Self(path.to_path_buf())
125 }
126
127 pub fn unchecked<P: AsRef<Path>>(path: P) -> Self {
129 Self(path.as_ref().to_path_buf())
130 }
131}
132
133impl Rewriter for Prefix {
134 fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
135 opt.map(|r| match r {
136 Rewrite::File(f) => Rewrite::File(f.map_path(|p| self.0.join(p))),
137 Rewrite::Redirect(r) => Rewrite::Redirect(r),
138 })
139 }
140}
141
142impl Rewriter for PathBuf {
143 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
144 Some(Rewrite::File(File::new(self.clone())))
145 }
146}
147
148pub struct TrailingDirs;
163
164impl Rewriter for TrailingDirs {
165 fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, req: &Request<'_>) -> Option<Rewrite<'r>> {
166 if let Some(Rewrite::File(f)) = &opt {
167 if !req.uri().path().ends_with('/') && f.path.is_dir() {
168 let uri = req.uri().clone().into_owned();
169 let uri = uri.map_path(|p| format!("{p}/")).unwrap();
170 return Some(Rewrite::Redirect(Redirect::temporary(uri)));
171 }
172 }
173
174 opt
175 }
176}
177
178pub struct DirIndex {
193 path: PathBuf,
194 check: bool,
195}
196
197impl DirIndex {
198 pub fn unconditional(path: impl AsRef<Path>) -> Self {
200 Self { path: path.as_ref().to_path_buf(), check: false }
201 }
202
203 pub fn if_exists(path: impl AsRef<Path>) -> Self {
205 Self { path: path.as_ref().to_path_buf(), check: true }
206 }
207}
208
209impl Rewriter for DirIndex {
210 fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
211 match opt? {
212 Rewrite::File(f) if f.path.is_dir() => {
213 let candidate = f.path.join(&self.path);
214 if self.check && !candidate.is_file() {
215 return Some(Rewrite::File(f));
216 }
217
218 Some(Rewrite::File(f.map_path(|_| candidate)))
219 }
220 r => Some(r),
221 }
222 }
223}
224
225impl<'r> From<File<'r>> for Rewrite<'r> {
226 fn from(value: File<'r>) -> Self {
227 Self::File(value)
228 }
229}
230
231impl<'r> From<Redirect> for Rewrite<'r> {
232 fn from(value: Redirect) -> Self {
233 Self::Redirect(value)
234 }
235}
236
237impl<F: Send + Sync + 'static> Rewriter for F
238 where F: for<'r> Fn(Option<Rewrite<'r>>, &Request<'_>) -> Option<Rewrite<'r>>
239{
240 fn rewrite<'r>(&self, f: Option<Rewrite<'r>>, r: &Request<'_>) -> Option<Rewrite<'r>> {
241 self(f, r)
242 }
243}
244
245impl Rewriter for Rewrite<'static> {
246 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
247 Some(self.clone())
248 }
249}
250
251impl Rewriter for File<'static> {
252 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
253 Some(Rewrite::File(self.clone()))
254 }
255}
256
257impl Rewriter for Redirect {
258 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
259 Some(Rewrite::Redirect(self.clone()))
260 }
261}