rocket_community/fs/
rewrite.rs1use std::borrow::Cow;
2use std::path::{Path, PathBuf};
3
4use crate::http::{ext::IntoOwned, HeaderMap};
5use crate::response::Redirect;
6use crate::Request;
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 {
51 path: path.into(),
52 headers: HeaderMap::new(),
53 }
54 }
55
56 pub fn checked<P: AsRef<Path>>(path: P) -> Self {
62 let path = path.as_ref();
63 if !path.exists() {
64 let path = path.display();
65 error!(%path, "FileServer path does not exist.\n\
66 Panicking to prevent inevitable handler error.");
67 panic!("missing file {}: refusing to continue", path);
68 }
69
70 Self::new(path.to_path_buf())
71 }
72
73 pub fn map_path<F, P>(self, f: F) -> Self
75 where
76 F: FnOnce(Cow<'r, Path>) -> P,
77 P: Into<Cow<'r, Path>>,
78 {
79 Self {
80 path: f(self.path).into(),
81 headers: self.headers,
82 }
83 }
84
85 pub fn is_hidden(&self) -> bool {
94 self.path
95 .iter()
96 .any(|s| s.as_encoded_bytes().starts_with(b"."))
97 }
98
99 pub fn is_visible(&self) -> bool {
102 !self.is_hidden()
103 }
104}
105
106pub struct Prefix(PathBuf);
120
121impl Prefix {
122 pub fn checked<P: AsRef<Path>>(path: P) -> Self {
124 let path = path.as_ref();
125 if !path.is_dir() {
126 let path = path.display();
127 error!(%path, "FileServer path is not a directory.");
128 warn!("Aborting early to prevent inevitable handler error.");
129 panic!("invalid directory: refusing to continue");
130 }
131
132 Self(path.to_path_buf())
133 }
134
135 pub fn unchecked<P: AsRef<Path>>(path: P) -> Self {
137 Self(path.as_ref().to_path_buf())
138 }
139}
140
141impl Rewriter for Prefix {
142 fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
143 opt.map(|r| match r {
144 Rewrite::File(f) => Rewrite::File(f.map_path(|p| self.0.join(p))),
145 Rewrite::Redirect(r) => Rewrite::Redirect(r),
146 })
147 }
148}
149
150impl Rewriter for PathBuf {
151 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
152 Some(Rewrite::File(File::new(self.clone())))
153 }
154}
155
156pub struct TrailingDirs;
172
173impl Rewriter for TrailingDirs {
174 fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, req: &Request<'_>) -> Option<Rewrite<'r>> {
175 if let Some(Rewrite::File(f)) = &opt {
176 if !req.uri().path().ends_with('/') && f.path.is_dir() {
177 let uri = req.uri().clone().into_owned();
178 let uri = uri.map_path(|p| format!("{p}/")).unwrap();
179 return Some(Rewrite::Redirect(Redirect::temporary(uri)));
180 }
181 }
182
183 opt
184 }
185}
186
187pub struct DirIndex {
203 path: PathBuf,
204 check: bool,
205}
206
207impl DirIndex {
208 pub fn unconditional(path: impl AsRef<Path>) -> Self {
210 Self {
211 path: path.as_ref().to_path_buf(),
212 check: false,
213 }
214 }
215
216 pub fn if_exists(path: impl AsRef<Path>) -> Self {
218 Self {
219 path: path.as_ref().to_path_buf(),
220 check: true,
221 }
222 }
223}
224
225impl Rewriter for DirIndex {
226 fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
227 match opt? {
228 Rewrite::File(f) if f.path.is_dir() => {
229 let candidate = f.path.join(&self.path);
230 if self.check && !candidate.is_file() {
231 return Some(Rewrite::File(f));
232 }
233
234 Some(Rewrite::File(f.map_path(|_| candidate)))
235 }
236 r => Some(r),
237 }
238 }
239}
240
241impl<'r> From<File<'r>> for Rewrite<'r> {
242 fn from(value: File<'r>) -> Self {
243 Self::File(value)
244 }
245}
246
247impl<'r> From<Redirect> for Rewrite<'r> {
248 fn from(value: Redirect) -> Self {
249 Self::Redirect(value)
250 }
251}
252
253impl<F: Send + Sync + 'static> Rewriter for F
254where
255 F: for<'r> Fn(Option<Rewrite<'r>>, &Request<'_>) -> Option<Rewrite<'r>>,
256{
257 fn rewrite<'r>(&self, f: Option<Rewrite<'r>>, r: &Request<'_>) -> Option<Rewrite<'r>> {
258 self(f, r)
259 }
260}
261
262impl Rewriter for Rewrite<'static> {
263 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
264 Some(self.clone())
265 }
266}
267
268impl Rewriter for File<'static> {
269 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
270 Some(Rewrite::File(self.clone()))
271 }
272}
273
274impl Rewriter for Redirect {
275 fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
276 Some(Rewrite::Redirect(self.clone()))
277 }
278}