Skip to content

Commit 30610e0

Browse files
committed
Fix issues, and clean up implementation
1 parent 4a40a7b commit 30610e0

File tree

2 files changed

+72
-27
lines changed

2 files changed

+72
-27
lines changed

core/lib/src/fs/server.rs

+71-26
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use core::fmt;
2+
use std::any::{type_name, Any, TypeId};
23
use std::path::{PathBuf, Path};
34
use std::sync::Arc;
45
use std::time::SystemTime;
56

67

78
use crate::{Data, Request};
8-
use crate::http::{Method, Status, uri::{Segments, Uri}, ext::IntoOwned, HeaderMap, Header};
9+
use crate::http::{Method, Status, uri::{Segments, Reference}, ext::IntoOwned, HeaderMap};
910
use crate::route::{Route, Handler, Outcome};
1011
use crate::response::{Redirect, Responder};
1112
use crate::outcome::IntoOutcome;
@@ -92,20 +93,22 @@ impl fmt::Debug for DebugListRewrite<'_> {
9293
}
9394
}
9495

95-
pub trait Rewrite: Send + Sync + 'static {
96-
fn name(&self) -> &'static str {
97-
std::any::type_name::<Self>()
98-
}
96+
pub trait Rewrite: Send + Sync + Any {
9997
/// Modify RewritablePath as needed.
10098
fn rewrite(&self, req: &Request<'_>, path: FileServerResponse, root: &Path) -> FileServerResponse;
99+
/// Allow multiple of the same rewrite
100+
fn allow_multiple(&self) -> bool { false }
101+
fn name(&self) -> &'static str{ type_name::<Self>()}
101102
}
102103

104+
#[derive(Debug)]
103105
pub enum HiddenReason {
104106
DotFile,
105107
PermissionDenied,
106108
Other,
107109
}
108110

111+
#[derive(Debug)]
109112
pub enum FileServerResponse {
110113
/// Status: Ok
111114
File {
@@ -118,9 +121,9 @@ pub enum FileServerResponse {
118121
/// Status: NotFound (used to identify if the file does actually exist)
119122
Hidden { name: PathBuf, reason: HiddenReason },
120123
/// Status: Redirect
121-
PermanentRedirect { to: Uri<'static> },
124+
PermanentRedirect { to: Reference<'static> },
122125
/// Status: Redirect (TODO: should we allow this?)
123-
TemporaryRedirect { to: Uri<'static> },
126+
TemporaryRedirect { to: Reference<'static> },
124127
}
125128

126129
// These might have to remain as basic options (always processed first)
@@ -142,30 +145,35 @@ impl Rewrite for DotFiles {
142145

143146
struct Index(&'static str);
144147
impl Rewrite for Index {
145-
fn rewrite(&self, _req: &Request<'_>, path: FileServerResponse, _root: &Path) -> FileServerResponse {
148+
fn rewrite(&self, _req: &Request<'_>, path: FileServerResponse, root: &Path) -> FileServerResponse {
146149
// if path.file_name_path.is_dir() {
147150
// path.file_name_path.push(self.0);
148151
// // TODO: handle file_data_path
149152
// }
150153
match path {
151-
FileServerResponse::File { name, modified, headers } if name.is_dir() => FileServerResponse::File { name: name.join(self.0), modified, headers },
154+
FileServerResponse::File { name, modified, headers } if root.join(&name).is_dir() => FileServerResponse::File { name: name.join(self.0), modified, headers },
152155
path => path,
153156
}
154157
}
155158
}
156-
// I'm not sure this one works, since we should check it during startup
159+
// Actually, curiously, this already just works as-is (the only thing that prevents it is the startup check)
157160
struct IndexFile;
158161
impl Rewrite for IndexFile {
159-
fn rewrite(&self, req: &Request<'_>, path: FileServerResponse, root: &Path) -> FileServerResponse {
160-
todo!()
162+
fn rewrite(&self, _req: &Request<'_>, path: FileServerResponse, _root: &Path) -> FileServerResponse {
163+
match path {
164+
// FileServerResponse::File { name, modified, headers } if _root.is_file() && name.iter().count() == 0 => {
165+
// FileServerResponse::File { name, modified, headers }
166+
// }
167+
path => path,
168+
}
161169
}
162170
}
163171

164172
struct NormalizeDirs;
165173
impl Rewrite for NormalizeDirs {
166-
fn rewrite(&self, req: &Request<'_>, path: FileServerResponse, _root: &Path) -> FileServerResponse {
174+
fn rewrite(&self, req: &Request<'_>, path: FileServerResponse, root: &Path) -> FileServerResponse {
167175
match path {
168-
FileServerResponse::File { name, .. } if name.is_dir() && !req.uri().path().ends_with('/') =>
176+
FileServerResponse::File { name, .. } if !req.uri().path().ends_with('/') && root.join(&name).is_dir() =>
169177
FileServerResponse::PermanentRedirect {
170178
to: req.uri().map_path(|p| format!("{}/", p))
171179
.expect("adding a trailing slash to a known good path => valid path")
@@ -218,7 +226,9 @@ impl FileServer {
218226
/// ```
219227
#[track_caller]
220228
pub fn from<P: AsRef<Path>>(path: P) -> Self {
221-
FileServer::new(path, Options::default())
229+
FileServer::new(path, Options::None)
230+
.rewrite(NormalizeDirs)
231+
.rewrite(Index("index.html"))
222232
}
223233

224234
/// Constructs a new `FileServer` that serves files from the file system
@@ -255,6 +265,7 @@ impl FileServer {
255265

256266
let path = path.as_ref();
257267
if !options.contains(Options::Missing) {
268+
#[allow(deprecated)]
258269
if !options.contains(Options::IndexFile) && !path.is_dir() {
259270
let path = path.display();
260271
error!("FileServer path '{}' is not a directory.", path.primary());
@@ -267,12 +278,34 @@ impl FileServer {
267278
panic!("invalid file: refusing to continue");
268279
}
269280
}
281+
let mut rewrites: Vec<Arc<dyn Rewrite>> = vec![];
282+
#[allow(deprecated)]
283+
if options.contains(Options::DotFiles) {
284+
rewrites.push(Arc::new(DotFiles));
285+
}
286+
#[allow(deprecated)]
287+
if options.contains(Options::NormalizeDirs) {
288+
rewrites.push(Arc::new(NormalizeDirs));
289+
}
290+
#[allow(deprecated)]
291+
if options.contains(Options::Index) {
292+
rewrites.push(Arc::new(Index("index.html")));
293+
}
294+
295+
FileServer { root: path.into(), options, rewrites, rank: Self::DEFAULT_RANK }
296+
}
270297

271-
FileServer { root: path.into(), options, rewrites: vec![], rank: Self::DEFAULT_RANK }
298+
pub fn remove_rewrites<T: Rewrite>(mut self) -> Self {
299+
self.rewrites.retain(|r| r.as_ref().type_id() != TypeId::of::<T>());
300+
self
272301
}
273302

274303
pub fn rewrite(mut self, rewrite: impl Rewrite) -> Self {
275-
self.rewrites.push(Arc::new(rewrite));
304+
if rewrite.allow_multiple() || !self.rewrites.iter().any(|f| f.as_ref().type_id() == rewrite.type_id()) {
305+
self.rewrites.push(Arc::new(rewrite));
306+
} else {
307+
error!("Attempted to insert multiple of the same rewrite `{}` on a FileServer", rewrite.name());
308+
}
276309
self
277310
}
278311

@@ -317,20 +350,28 @@ impl Handler for FileServer {
317350
Some((name, true)) => FileServerResponse::Hidden { name, reason: HiddenReason::DotFile },
318351
None => return Outcome::forward(data, Status::NotFound),
319352
};
353+
println!("initial: {response:?}");
320354
for rewrite in &self.rewrites {
321355
response = rewrite.rewrite(req, response, &self.root);
356+
println!("after: {} {response:?}", rewrite.name());
322357
}
323358
match response {
324-
FileServerResponse::File { name, modified, headers } => NamedFile::open(self.root.join(name)).await.respond_to(req).map(|mut r| {
325-
for header in headers {
326-
r.adjoin_raw_header(header.name.as_str().to_owned(), header.value);
327-
}
328-
if let Some(modified) = modified {
329-
// TODO: must be converted to http-data format
330-
// r.set_header(Header::new("Last-Modified", format!("{:?}", modified)));
359+
FileServerResponse::File { name, modified, headers } => {
360+
let path = self.root.join(name);
361+
if path.is_dir() {
362+
return Outcome::Forward((data, Status::NotFound));
331363
}
332-
r
333-
}).or_forward((data, Status::NotFound)),
364+
NamedFile::open(path).await.respond_to(req).map(|mut r| {
365+
for header in headers {
366+
r.adjoin_raw_header(header.name.as_str().to_owned(), header.value);
367+
}
368+
if let Some(modified) = modified {
369+
// TODO: must be converted to http-date format
370+
// r.set_header(Header::new("Last-Modified", format!("{:?}", modified)));
371+
}
372+
r
373+
}).or_forward((data, Status::NotFound))
374+
},
334375
FileServerResponse::Hidden { .. } | FileServerResponse::NotFound { ..} => Outcome::forward(data, Status::NotFound),
335376
FileServerResponse::PermanentRedirect { to } => Redirect::permanent(to).respond_to(req).or_forward((data, Status::InternalServerError)),
336377
FileServerResponse::TemporaryRedirect { to } => Redirect::temporary(to).respond_to(req).or_forward((data, Status::InternalServerError)),
@@ -372,6 +413,7 @@ impl Options {
372413
/// exists. When disabled, requests to directories will always forward.
373414
///
374415
/// **Enabled by default.**
416+
#[deprecated(note = "Replaced by `.rewrite(Index(\"index.html\"))`")]
375417
pub const Index: Options = Options(1 << 0);
376418

377419
/// Allow serving dotfiles.
@@ -381,6 +423,7 @@ impl Options {
381423
/// treated as missing.
382424
///
383425
/// **Disabled by default.**
426+
#[deprecated(note = "Replaced by `.rewrite(DotFiles)`")]
384427
pub const DotFiles: Options = Options(1 << 1);
385428

386429
/// Normalizes directory requests by redirecting requests to directory paths
@@ -420,6 +463,7 @@ impl Options {
420463
/// redirected. `index.html` would be rendered, and the relative link to
421464
/// `cat.jpeg` would be resolved by the browser as `example.com/cat.jpeg`.
422465
/// Rocket would thus try to find `/static/cat.jpeg`, which does not exist.
466+
#[deprecated(note = "Replaced by `.rewrite(NormalizeDirs)`")]
423467
pub const NormalizeDirs: Options = Options(1 << 2);
424468

425469
/// Allow serving a file instead of a directory.
@@ -486,6 +530,7 @@ impl Options {
486530

487531
/// The default set of options: `Options::Index | Options:NormalizeDirs`.
488532
impl Default for Options {
533+
#[allow(deprecated)]
489534
fn default() -> Self {
490535
Options::Index | Options::NormalizeDirs
491536
}

core/lib/tests/file_server.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ fn rocket() -> Rocket<Build> {
1414
let root = static_root();
1515
rocket::build()
1616
.mount("/default", FileServer::from(&root))
17-
.mount("/no_index", FileServer::new(&root, Options::None))
17+
.mount("/no_index", dbg!(FileServer::new(&root, Options::None)))
1818
.mount("/dots", FileServer::new(&root, Options::DotFiles))
1919
.mount("/index", FileServer::new(&root, Options::Index))
2020
.mount("/both", FileServer::new(&root, Options::DotFiles | Options::Index))

0 commit comments

Comments
 (0)