@@ -97,11 +97,13 @@ pub trait Rewrite: Send + Sync + Any {
97
97
-> FileServerResponse ;
98
98
/// Allow multiple of the same rewrite
99
99
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 > ( ) }
101
102
}
102
103
103
104
#[ derive( Debug ) ]
104
105
pub enum HiddenReason {
106
+ DoesNotExist ,
105
107
DotFile ,
106
108
PermissionDenied ,
107
109
Other ,
@@ -111,28 +113,27 @@ pub enum HiddenReason {
111
113
pub enum FileServerResponse {
112
114
/// Status: Ok
113
115
File {
114
- name : PathBuf ,
116
+ path : PathBuf ,
115
117
headers : HeaderMap < ' static > ,
116
118
} ,
117
119
/// 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 } ,
121
121
/// Status: Redirect
122
122
PermanentRedirect { to : Reference < ' static > } ,
123
123
/// Status: Redirect (TODO: should we allow this?)
124
124
TemporaryRedirect { to : Reference < ' static > } ,
125
125
}
126
126
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.
128
129
pub struct DotFiles ;
129
130
impl Rewrite for DotFiles {
130
131
fn rewrite ( & self , _req : & Request < ' _ > , path : FileServerResponse , _root : & Path )
131
132
-> FileServerResponse
132
133
{
133
134
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 ( ) } ,
136
137
path => path,
137
138
}
138
139
}
@@ -144,18 +145,32 @@ impl Rewrite for DotFiles {
144
145
// }
145
146
// }
146
147
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`
147
155
pub struct Index ( pub & ' static str ) ;
148
156
impl Rewrite for Index {
149
- fn rewrite ( & self , _req : & Request < ' _ > , path : FileServerResponse , root : & Path )
157
+ fn rewrite ( & self , _req : & Request < ' _ > , path : FileServerResponse , _root : & Path )
150
158
-> FileServerResponse
151
159
{
152
160
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 } ,
155
163
path => path,
156
164
}
157
165
}
158
166
}
167
+ impl Default for Index {
168
+ fn default ( ) -> Self {
169
+ Self ( "index.html" )
170
+ }
171
+ }
172
+
173
+
159
174
// Actually, curiously, this already just works as-is (the only thing that prevents
160
175
// it is the startup check)
161
176
pub struct IndexFile ;
@@ -169,14 +184,22 @@ impl Rewrite for IndexFile {
169
184
}
170
185
}
171
186
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`
172
195
pub struct NormalizeDirs ;
173
196
impl Rewrite for NormalizeDirs {
174
- fn rewrite ( & self , req : & Request < ' _ > , path : FileServerResponse , root : & Path )
197
+ fn rewrite ( & self , req : & Request < ' _ > , path : FileServerResponse , _root : & Path )
175
198
-> FileServerResponse
176
199
{
177
200
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 ( ) =>
180
203
FileServerResponse :: PermanentRedirect {
181
204
to : req. uri ( ) . map_path ( |p| format ! ( "{}/" , p) )
182
205
. expect ( "adding a trailing slash to a known good path => valid path" )
@@ -298,14 +321,33 @@ impl FileServer {
298
321
FileServer { root : path. into ( ) , options, rewrites, rank : Self :: DEFAULT_RANK }
299
322
}
300
323
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.
301
328
pub fn remove_rewrites < T : Rewrite > ( mut self ) -> Self {
302
329
self . rewrites . retain ( |r| r. as_ref ( ) . type_id ( ) != TypeId :: of :: < T > ( ) ) ;
303
330
self
304
331
}
305
332
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.
306
348
pub fn rewrite ( mut self , rewrite : impl Rewrite ) -> Self {
307
349
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 ( ) ) {
309
351
self . rewrites . push ( Arc :: new ( rewrite) ) ;
310
352
} else {
311
353
error ! (
@@ -348,25 +390,32 @@ impl From<FileServer> for Vec<Route> {
348
390
impl Handler for FileServer {
349
391
async fn handle < ' r > ( & self , req : & ' r Request < ' _ > , data : Data < ' r > ) -> Outcome < ' r > {
350
392
use crate :: http:: uri:: fmt:: Path as UriPath ;
351
- // let options = self.options;
352
393
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 ) ) ;
355
396
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 } ,
360
401
None => return Outcome :: forward ( data, Status :: NotFound ) ,
361
402
} ;
362
- println ! ( "initial: {response:?}" ) ;
403
+ // println!("initial: {response:?}");
363
404
for rewrite in & self . rewrites {
364
405
response = rewrite. rewrite ( req, response, & self . root ) ;
365
- println ! ( "after: {} {response:?}" , rewrite. name( ) ) ;
406
+ // println!("after: {} {response:?}", rewrite.name());
366
407
}
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.
367
417
match response {
368
- FileServerResponse :: File { name, headers } => {
369
- let path = self . root . join ( name) ;
418
+ FileServerResponse :: File { path, headers } => {
370
419
if path. is_dir ( ) {
371
420
return Outcome :: Forward ( ( data, Status :: NotFound ) ) ;
372
421
}
@@ -377,8 +426,7 @@ impl Handler for FileServer {
377
426
r
378
427
} ) . or_forward ( ( data, Status :: NotFound ) )
379
428
} ,
380
- FileServerResponse :: Hidden { .. } | FileServerResponse :: NotFound { ..} =>
381
- Outcome :: forward ( data, Status :: NotFound ) ,
429
+ FileServerResponse :: NotFound { .. } => Outcome :: forward ( data, Status :: NotFound ) ,
382
430
FileServerResponse :: PermanentRedirect { to } => Redirect :: permanent ( to)
383
431
. respond_to ( req)
384
432
. or_forward ( ( data, Status :: InternalServerError ) ) ,
0 commit comments