Skip to content

Commit d1393e3

Browse files
committed
Minor cleanup and adding documentation
1 parent bd7994e commit d1393e3

File tree

1 file changed

+76
-28
lines changed

1 file changed

+76
-28
lines changed

core/lib/src/fs/server.rs

+76-28
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,13 @@ pub trait Rewrite: Send + Sync + Any {
9797
-> FileServerResponse;
9898
/// Allow multiple of the same rewrite
9999
fn allow_multiple(&self) -> bool { false }
100-
fn name(&self) -> &'static str{ type_name::<Self>()}
100+
/// provides type name for debug printing
101+
fn name(&self) -> &'static str { type_name::<Self>() }
101102
}
102103

103104
#[derive(Debug)]
104105
pub enum HiddenReason {
106+
DoesNotExist,
105107
DotFile,
106108
PermissionDenied,
107109
Other,
@@ -111,28 +113,27 @@ pub enum HiddenReason {
111113
pub enum FileServerResponse {
112114
/// Status: Ok
113115
File {
114-
name: PathBuf,
116+
path: PathBuf,
115117
headers: HeaderMap<'static>,
116118
},
117119
/// Status: NotFound
118-
NotFound { name: PathBuf },
119-
/// Status: NotFound (used to identify if the file does actually exist)
120-
Hidden { name: PathBuf, reason: HiddenReason },
120+
NotFound { path: PathBuf, reason: HiddenReason },
121121
/// Status: Redirect
122122
PermanentRedirect { to: Reference<'static> },
123123
/// Status: Redirect (TODO: should we allow this?)
124124
TemporaryRedirect { to: Reference<'static> },
125125
}
126126

127-
// These might have to remain as basic options (always processed first)
127+
/// Rewrites the path to allow paths that include a component beginning with a
128+
/// a `.`. This rewrite should be applied first, before any other rewrite.
128129
pub struct DotFiles;
129130
impl Rewrite for DotFiles {
130131
fn rewrite(&self, _req: &Request<'_>, path: FileServerResponse, _root: &Path)
131132
-> FileServerResponse
132133
{
133134
match path {
134-
FileServerResponse::Hidden { name, reason: HiddenReason::DotFile } =>
135-
FileServerResponse::File { name, headers: HeaderMap::new() },
135+
FileServerResponse::NotFound { path: name, reason: HiddenReason::DotFile } =>
136+
FileServerResponse::File { path: name, headers: HeaderMap::new() },
136137
path => path,
137138
}
138139
}
@@ -144,18 +145,32 @@ impl Rewrite for DotFiles {
144145
// }
145146
// }
146147

148+
/// Rewrites a path to a directory to return the content of an index file, `index.html`
149+
/// by defualt.
150+
///
151+
/// # Examples
152+
/// - Rewrites `/` to `/index.html`
153+
/// - Rewrites `/home/` to `/home/index.html`
154+
/// - Does not rewrite `/home/test.html`
147155
pub struct Index(pub &'static str);
148156
impl Rewrite for Index {
149-
fn rewrite(&self, _req: &Request<'_>, path: FileServerResponse, root: &Path)
157+
fn rewrite(&self, _req: &Request<'_>, path: FileServerResponse, _root: &Path)
150158
-> FileServerResponse
151159
{
152160
match path {
153-
FileServerResponse::File { name, headers } if root.join(&name).is_dir() =>
154-
FileServerResponse::File { name: name.join(self.0), headers },
161+
FileServerResponse::File { path: name, headers } if name.is_dir() =>
162+
FileServerResponse::File { path: name.join(self.0), headers },
155163
path => path,
156164
}
157165
}
158166
}
167+
impl Default for Index {
168+
fn default() -> Self {
169+
Self("index.html")
170+
}
171+
}
172+
173+
159174
// Actually, curiously, this already just works as-is (the only thing that prevents
160175
// it is the startup check)
161176
pub struct IndexFile;
@@ -169,14 +184,22 @@ impl Rewrite for IndexFile {
169184
}
170185
}
171186

187+
/// Rewrites a path to a directory without a trailing slash to a redirect to
188+
/// the same directory with a trailing slash. This rewrite needs to be applied
189+
/// before [`Index`] and any other rewrite that changes the path to a file.
190+
///
191+
/// # Examples
192+
/// - Redirects `/home/test` to `/home/test/`
193+
/// - Does not redirect `/home/`
194+
/// - Does not redirect `/home/index.html`
172195
pub struct NormalizeDirs;
173196
impl Rewrite for NormalizeDirs {
174-
fn rewrite(&self, req: &Request<'_>, path: FileServerResponse, root: &Path)
197+
fn rewrite(&self, req: &Request<'_>, path: FileServerResponse, _root: &Path)
175198
-> FileServerResponse
176199
{
177200
match path {
178-
FileServerResponse::File { name, .. } if !req.uri().path().ends_with('/') &&
179-
root.join(&name).is_dir() =>
201+
FileServerResponse::File { path: name, .. } if !req.uri().path().ends_with('/') &&
202+
name.is_dir() =>
180203
FileServerResponse::PermanentRedirect {
181204
to: req.uri().map_path(|p| format!("{}/", p))
182205
.expect("adding a trailing slash to a known good path => valid path")
@@ -298,14 +321,33 @@ impl FileServer {
298321
FileServer { root: path.into(), options, rewrites, rank: Self::DEFAULT_RANK }
299322
}
300323

324+
/// Removes all rewrites of a specific type.
325+
///
326+
/// Ideally, this shouldn't exist, and it should be possible to always just not add
327+
/// the rewrites you don't want.
301328
pub fn remove_rewrites<T: Rewrite>(mut self) -> Self {
302329
self.rewrites.retain(|r| r.as_ref().type_id() != TypeId::of::<T>());
303330
self
304331
}
305332

333+
/// Add a rewrite step to this `FileServer`. The order in which rewrites are added can make
334+
/// a difference, since they are applied in the order they appear.
335+
///
336+
/// # Example
337+
///
338+
/// ```rust,no_compile
339+
/// # // TODO: turn this into a proper test
340+
/// FileServer::new("static/", Options::None)
341+
/// .rewrite(NormalizeDirs)
342+
/// .rewrite(Index::default())
343+
/// ```
344+
/// In this example, order actually does matter. [`NormalizeDirs`] will convert a path to a
345+
/// directory without a trailing slash into a redirect to the same path, but with the trailing
346+
/// slash. However, if the [`Index`] rewrite is applied first, the path will have been changed
347+
/// to the `index.html` file, causing [`NormalizeDirs`] to do nothing.
306348
pub fn rewrite(mut self, rewrite: impl Rewrite) -> Self {
307349
if rewrite.allow_multiple() ||
308-
!self.rewrites .iter().any(|f| f.as_ref().type_id() == rewrite.type_id()) {
350+
!self.rewrites.iter().any(|f| f.as_ref().type_id() == rewrite.type_id()) {
309351
self.rewrites.push(Arc::new(rewrite));
310352
} else {
311353
error!(
@@ -348,25 +390,32 @@ impl From<FileServer> for Vec<Route> {
348390
impl Handler for FileServer {
349391
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> {
350392
use crate::http::uri::fmt::Path as UriPath;
351-
// let options = self.options;
352393
let path = req.segments::<Segments<'_, UriPath>>(0..).ok()
353-
.and_then(|segments| segments.to_path_buf_dotfiles().ok());
354-
// .map(|path| self.root.join(path));
394+
.and_then(|segments| segments.to_path_buf_dotfiles().ok())
395+
.map(|(path, dots)| (self.root.join(path), dots));
355396
let mut response = match path {
356-
Some((name, false)) =>
357-
FileServerResponse::File { name, headers: HeaderMap::new() },
358-
Some((name, true)) =>
359-
FileServerResponse::Hidden { name, reason: HiddenReason::DotFile },
397+
Some((path, false)) =>
398+
FileServerResponse::File { path, headers: HeaderMap::new() },
399+
Some((path, true)) =>
400+
FileServerResponse::NotFound { path, reason: HiddenReason::DotFile },
360401
None => return Outcome::forward(data, Status::NotFound),
361402
};
362-
println!("initial: {response:?}");
403+
// println!("initial: {response:?}");
363404
for rewrite in &self.rewrites {
364405
response = rewrite.rewrite(req, response, &self.root);
365-
println!("after: {} {response:?}", rewrite.name());
406+
// println!("after: {} {response:?}", rewrite.name());
366407
}
408+
// Open TODOs:
409+
// - Should we validate the location of the path? We've aleady removed any
410+
// `..` and other traversal mechanisms, before passing to the rewrites.
411+
// - Should we allow more control? I think we should require the payload
412+
// to be a file on the disk, and the only thing left to control would be
413+
// headers (which we allow configuring), and status, which we allow a specific
414+
// set.
415+
// - Should we prepend the path root? I think we should, since I don't think the
416+
// relative path is particularly useful.
367417
match response {
368-
FileServerResponse::File { name, headers } => {
369-
let path = self.root.join(name);
418+
FileServerResponse::File { path, headers } => {
370419
if path.is_dir() {
371420
return Outcome::Forward((data, Status::NotFound));
372421
}
@@ -377,8 +426,7 @@ impl Handler for FileServer {
377426
r
378427
}).or_forward((data, Status::NotFound))
379428
},
380-
FileServerResponse::Hidden { .. } | FileServerResponse::NotFound { ..} =>
381-
Outcome::forward(data, Status::NotFound),
429+
FileServerResponse::NotFound { .. } => Outcome::forward(data, Status::NotFound),
382430
FileServerResponse::PermanentRedirect { to } => Redirect::permanent(to)
383431
.respond_to(req)
384432
.or_forward((data, Status::InternalServerError)),

0 commit comments

Comments
 (0)