1
1
use core:: fmt;
2
+ use std:: any:: { type_name, Any , TypeId } ;
2
3
use std:: path:: { PathBuf , Path } ;
3
4
use std:: sync:: Arc ;
4
5
use std:: time:: SystemTime ;
5
6
6
7
7
8
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 } ;
9
10
use crate :: route:: { Route , Handler , Outcome } ;
10
11
use crate :: response:: { Redirect , Responder } ;
11
12
use crate :: outcome:: IntoOutcome ;
@@ -92,20 +93,22 @@ impl fmt::Debug for DebugListRewrite<'_> {
92
93
}
93
94
}
94
95
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 {
99
97
/// Modify RewritablePath as needed.
100
98
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 > ( ) }
101
102
}
102
103
104
+ #[ derive( Debug ) ]
103
105
pub enum HiddenReason {
104
106
DotFile ,
105
107
PermissionDenied ,
106
108
Other ,
107
109
}
108
110
111
+ #[ derive( Debug ) ]
109
112
pub enum FileServerResponse {
110
113
/// Status: Ok
111
114
File {
@@ -118,9 +121,9 @@ pub enum FileServerResponse {
118
121
/// Status: NotFound (used to identify if the file does actually exist)
119
122
Hidden { name : PathBuf , reason : HiddenReason } ,
120
123
/// Status: Redirect
121
- PermanentRedirect { to : Uri < ' static > } ,
124
+ PermanentRedirect { to : Reference < ' static > } ,
122
125
/// Status: Redirect (TODO: should we allow this?)
123
- TemporaryRedirect { to : Uri < ' static > } ,
126
+ TemporaryRedirect { to : Reference < ' static > } ,
124
127
}
125
128
126
129
// These might have to remain as basic options (always processed first)
@@ -142,30 +145,35 @@ impl Rewrite for DotFiles {
142
145
143
146
struct Index ( & ' static str ) ;
144
147
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 {
146
149
// if path.file_name_path.is_dir() {
147
150
// path.file_name_path.push(self.0);
148
151
// // TODO: handle file_data_path
149
152
// }
150
153
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 } ,
152
155
path => path,
153
156
}
154
157
}
155
158
}
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)
157
160
struct IndexFile ;
158
161
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
+ }
161
169
}
162
170
}
163
171
164
172
struct NormalizeDirs ;
165
173
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 {
167
175
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 ( ) =>
169
177
FileServerResponse :: PermanentRedirect {
170
178
to : req. uri ( ) . map_path ( |p| format ! ( "{}/" , p) )
171
179
. expect ( "adding a trailing slash to a known good path => valid path" )
@@ -218,7 +226,9 @@ impl FileServer {
218
226
/// ```
219
227
#[ track_caller]
220
228
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" ) )
222
232
}
223
233
224
234
/// Constructs a new `FileServer` that serves files from the file system
@@ -255,6 +265,7 @@ impl FileServer {
255
265
256
266
let path = path. as_ref ( ) ;
257
267
if !options. contains ( Options :: Missing ) {
268
+ #[ allow( deprecated) ]
258
269
if !options. contains ( Options :: IndexFile ) && !path. is_dir ( ) {
259
270
let path = path. display ( ) ;
260
271
error ! ( "FileServer path '{}' is not a directory." , path. primary( ) ) ;
@@ -267,12 +278,34 @@ impl FileServer {
267
278
panic ! ( "invalid file: refusing to continue" ) ;
268
279
}
269
280
}
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
+ }
270
297
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
272
301
}
273
302
274
303
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
+ }
276
309
self
277
310
}
278
311
@@ -317,20 +350,28 @@ impl Handler for FileServer {
317
350
Some ( ( name, true ) ) => FileServerResponse :: Hidden { name, reason : HiddenReason :: DotFile } ,
318
351
None => return Outcome :: forward ( data, Status :: NotFound ) ,
319
352
} ;
353
+ println ! ( "initial: {response:?}" ) ;
320
354
for rewrite in & self . rewrites {
321
355
response = rewrite. rewrite ( req, response, & self . root ) ;
356
+ println ! ( "after: {} {response:?}" , rewrite. name( ) ) ;
322
357
}
323
358
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 ) ) ;
331
363
}
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
+ } ,
334
375
FileServerResponse :: Hidden { .. } | FileServerResponse :: NotFound { ..} => Outcome :: forward ( data, Status :: NotFound ) ,
335
376
FileServerResponse :: PermanentRedirect { to } => Redirect :: permanent ( to) . respond_to ( req) . or_forward ( ( data, Status :: InternalServerError ) ) ,
336
377
FileServerResponse :: TemporaryRedirect { to } => Redirect :: temporary ( to) . respond_to ( req) . or_forward ( ( data, Status :: InternalServerError ) ) ,
@@ -372,6 +413,7 @@ impl Options {
372
413
/// exists. When disabled, requests to directories will always forward.
373
414
///
374
415
/// **Enabled by default.**
416
+ #[ deprecated( note = "Replaced by `.rewrite(Index(\" index.html\" ))`" ) ]
375
417
pub const Index : Options = Options ( 1 << 0 ) ;
376
418
377
419
/// Allow serving dotfiles.
@@ -381,6 +423,7 @@ impl Options {
381
423
/// treated as missing.
382
424
///
383
425
/// **Disabled by default.**
426
+ #[ deprecated( note = "Replaced by `.rewrite(DotFiles)`" ) ]
384
427
pub const DotFiles : Options = Options ( 1 << 1 ) ;
385
428
386
429
/// Normalizes directory requests by redirecting requests to directory paths
@@ -420,6 +463,7 @@ impl Options {
420
463
/// redirected. `index.html` would be rendered, and the relative link to
421
464
/// `cat.jpeg` would be resolved by the browser as `example.com/cat.jpeg`.
422
465
/// Rocket would thus try to find `/static/cat.jpeg`, which does not exist.
466
+ #[ deprecated( note = "Replaced by `.rewrite(NormalizeDirs)`" ) ]
423
467
pub const NormalizeDirs : Options = Options ( 1 << 2 ) ;
424
468
425
469
/// Allow serving a file instead of a directory.
@@ -486,6 +530,7 @@ impl Options {
486
530
487
531
/// The default set of options: `Options::Index | Options:NormalizeDirs`.
488
532
impl Default for Options {
533
+ #[ allow( deprecated) ]
489
534
fn default ( ) -> Self {
490
535
Options :: Index | Options :: NormalizeDirs
491
536
}
0 commit comments