1
1
use core:: fmt;
2
2
use std:: borrow:: Cow ;
3
- use std:: path:: { PathBuf , Path } ;
3
+ use std:: ffi:: OsStr ;
4
+ use std:: path:: { Path , PathBuf , MAIN_SEPARATOR_STR } ;
4
5
use std:: sync:: Arc ;
5
6
6
7
use crate :: fs:: NamedFile ;
@@ -116,11 +117,12 @@ impl fmt::Debug for DebugListRewrite<'_> {
116
117
/// - [`FileServer::map_file()`]
117
118
pub trait Rewriter : Send + Sync + ' static {
118
119
/// Alter the [`FileResponse`] as needed.
119
- fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > ) -> Option < FileResponse < ' p , ' h > > ;
120
+ fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > , req : & Request < ' _ > ) -> Option < FileResponse < ' p , ' h > > ;
120
121
}
121
122
122
123
/// A Response from a [`FileServer`]
123
124
#[ derive( Debug ) ]
125
+ #[ non_exhaustive]
124
126
pub enum FileResponse < ' p , ' h > {
125
127
/// Return the contents of the specified file.
126
128
File ( File < ' p , ' h > ) ,
@@ -157,8 +159,6 @@ impl<'p, 'h> From<Redirect> for Option<FileResponse<'p, 'h>> {
157
159
pub struct File < ' p , ' h > {
158
160
/// The path to the file that [`FileServer`] will respond with.
159
161
pub path : Cow < ' p , Path > ,
160
- /// The original uri from the [`Request`]
161
- pub full_uri : & ' p Origin < ' p > ,
162
162
/// A list of headers to be added to the generated response.
163
163
pub headers : HeaderMap < ' h > ,
164
164
}
@@ -174,7 +174,6 @@ impl<'p, 'h> File<'p, 'h> {
174
174
pub fn with_path ( self , path : impl Into < Cow < ' p , Path > > ) -> Self {
175
175
Self {
176
176
path : path. into ( ) ,
177
- full_uri : self . full_uri ,
178
177
headers : self . headers ,
179
178
}
180
179
}
@@ -183,33 +182,53 @@ impl<'p, 'h> File<'p, 'h> {
183
182
pub fn map_path < R : Into < Cow < ' p , Path > > > ( self , f : impl FnOnce ( Cow < ' p , Path > ) -> R ) -> Self {
184
183
Self {
185
184
path : f ( self . path ) . into ( ) ,
186
- full_uri : self . full_uri ,
187
185
headers : self . headers ,
188
186
}
189
187
}
190
188
191
- /// Convert this `File` into a Redirect, transforming the URI.
192
- pub fn into_redirect ( self , f : impl FnOnce ( Origin < ' static > ) -> Origin < ' static > )
193
- -> FileResponse < ' p , ' h >
194
- {
195
- FileResponse :: Redirect ( Redirect :: permanent ( f ( self . full_uri . clone ( ) . into_owned ( ) ) ) )
189
+ // /// Convert this `File` into a Redirect, transforming the URI.
190
+ // pub fn into_redirect(self, f: impl FnOnce(Origin<'static>) -> Origin<'static>)
191
+ // -> FileResponse<'p, 'h>
192
+ // {
193
+ // FileResponse::Redirect(Redirect::permanent(f(self.full_uri.clone().into_owned())))
194
+ // }
195
+
196
+ async fn respond_to < ' r > ( self , req : & ' r Request < ' _ > , data : Data < ' r > ) -> Outcome < ' r > where ' h : ' r {
197
+ /// Normalize paths to enable `file_root` to work properly
198
+ fn strip_trailing_slash ( p : & Path ) -> & Path {
199
+ let bytes = p. as_os_str ( ) . as_encoded_bytes ( ) ;
200
+ let bytes = bytes. strip_suffix ( MAIN_SEPARATOR_STR . as_bytes ( ) ) . unwrap_or ( bytes) ;
201
+ // SAFETY: Since we stripped a valid UTF-8 sequence (or left it unchanged),
202
+ // this is still a valid OsStr.
203
+ Path :: new ( unsafe { OsStr :: from_encoded_bytes_unchecked ( bytes) } )
204
+ }
205
+
206
+ NamedFile :: open ( strip_trailing_slash ( self . path . as_ref ( ) ) )
207
+ . await
208
+ . respond_to ( req)
209
+ . map ( |mut r| {
210
+ for header in self . headers {
211
+ r. adjoin_raw_header ( header. name . as_str ( ) . to_owned ( ) , header. value ) ;
212
+ }
213
+ r
214
+ } ) . or_forward ( ( data, Status :: NotFound ) )
196
215
}
197
216
}
198
217
199
218
impl < F : Send + Sync + ' static > Rewriter for F
200
- where F : for <' r , ' h > Fn ( Option < FileResponse < ' r , ' h > > ) -> Option < FileResponse < ' r , ' h > >
219
+ where F : for <' r , ' h > Fn ( Option < FileResponse < ' r , ' h > > , & Request < ' _ > ) -> Option < FileResponse < ' r , ' h > >
201
220
{
202
- fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > ) -> Option < FileResponse < ' p , ' h > > {
203
- self ( path)
221
+ fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > , req : & Request < ' _ > ) -> Option < FileResponse < ' p , ' h > > {
222
+ self ( path, req )
204
223
}
205
224
}
206
225
207
226
/// Helper to implement [`FileServer::filter_file()`]
208
227
struct FilterFile < F > ( F ) ;
209
- impl < F : Fn ( & File < ' _ , ' _ > ) -> bool + Send + Sync + ' static > Rewriter for FilterFile < F > {
210
- fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > ) -> Option < FileResponse < ' p , ' h > > {
228
+ impl < F : Fn ( & File < ' _ , ' _ > , & Request < ' _ > ) -> bool + Send + Sync + ' static > Rewriter for FilterFile < F > {
229
+ fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > , req : & Request < ' _ > ) -> Option < FileResponse < ' p , ' h > > {
211
230
match path {
212
- Some ( FileResponse :: File ( file) ) if !self . 0 ( & file) => None ,
231
+ Some ( FileResponse :: File ( file) ) if !self . 0 ( & file, req ) => None ,
213
232
path => path,
214
233
}
215
234
}
@@ -218,11 +237,11 @@ impl<F: Fn(&File<'_, '_>) -> bool + Send + Sync + 'static> Rewriter for FilterFi
218
237
/// Helper to implement [`FileServer::map_file()`]
219
238
struct MapFile < F > ( F ) ;
220
239
impl < F > Rewriter for MapFile < F >
221
- where F : for < ' p , ' h > Fn ( File < ' p , ' h > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static ,
240
+ where F : for < ' p , ' h > Fn ( File < ' p , ' h > , & Request < ' _ > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static ,
222
241
{
223
- fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > ) -> Option < FileResponse < ' p , ' h > > {
242
+ fn rewrite < ' p , ' h > ( & self , path : Option < FileResponse < ' p , ' h > > , req : & Request < ' _ > ) -> Option < FileResponse < ' p , ' h > > {
224
243
match path {
225
- Some ( FileResponse :: File ( file) ) => Some ( self . 0 ( file) ) ,
244
+ Some ( FileResponse :: File ( file) ) => Some ( self . 0 ( file, req ) ) ,
226
245
path => path,
227
246
}
228
247
}
@@ -247,7 +266,7 @@ impl<F> Rewriter for MapFile<F>
247
266
///
248
267
/// Panics if `path` is not directory.
249
268
pub fn dir_root ( path : impl AsRef < Path > )
250
- -> impl for <' p , ' h > Fn ( File < ' p , ' h > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static
269
+ -> impl for <' p , ' h > Fn ( File < ' p , ' h > , & Request < ' _ > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static
251
270
{
252
271
use yansi:: Paint as _;
253
272
@@ -259,7 +278,7 @@ pub fn dir_root(path: impl AsRef<Path>)
259
278
panic ! ( "invalid directory: refusing to continue" ) ;
260
279
}
261
280
let path = path. to_path_buf ( ) ;
262
- move |f| {
281
+ move |f, _r | {
263
282
FileResponse :: File ( f. map_path ( |p| path. join ( p) ) )
264
283
}
265
284
}
@@ -280,7 +299,7 @@ pub fn dir_root(path: impl AsRef<Path>)
280
299
///
281
300
/// Panics if `path` does not exist.
282
301
pub fn file_root ( path : impl AsRef < Path > )
283
- -> impl for <' p , ' h > Fn ( File < ' p , ' h > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static
302
+ -> impl for <' p , ' h > Fn ( File < ' p , ' h > , & Request < ' _ > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static
284
303
{
285
304
use yansi:: Paint as _;
286
305
@@ -292,7 +311,7 @@ pub fn file_root(path: impl AsRef<Path>)
292
311
panic ! ( "invalid file: refusing to continue" ) ;
293
312
}
294
313
let path = path. to_path_buf ( ) ;
295
- move |f| {
314
+ move |f, _r | {
296
315
FileResponse :: File ( f. map_path ( |p| path. join ( p) ) )
297
316
}
298
317
}
@@ -310,10 +329,10 @@ pub fn file_root(path: impl AsRef<Path>)
310
329
/// # }
311
330
/// ```
312
331
pub fn missing_root ( path : impl AsRef < Path > )
313
- -> impl for <' p , ' h > Fn ( File < ' p , ' h > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static
332
+ -> impl for <' p , ' h > Fn ( File < ' p , ' h > , & Request < ' _ > ) -> FileResponse < ' p , ' h > + Send + Sync + ' static
314
333
{
315
334
let path = path. as_ref ( ) . to_path_buf ( ) ;
316
- move |f| {
335
+ move |f, _r | {
317
336
FileResponse :: File ( f. map_path ( |p| path. join ( p) ) )
318
337
}
319
338
}
@@ -332,7 +351,7 @@ pub fn missing_root(path: impl AsRef<Path>)
332
351
/// .map_file(dir_root("static"))
333
352
/// # }
334
353
/// ```
335
- pub fn filter_dotfiles ( file : & File < ' _ , ' _ > ) -> bool {
354
+ pub fn filter_dotfiles ( file : & File < ' _ , ' _ > , _req : & Request < ' _ > ) -> bool {
336
355
!file. path . iter ( ) . any ( |s| s. as_encoded_bytes ( ) . starts_with ( b"." ) )
337
356
}
338
357
@@ -352,10 +371,12 @@ pub fn filter_dotfiles(file: &File<'_, '_>) -> bool {
352
371
/// .map_file(normalize_dirs)
353
372
/// # }
354
373
/// ```
355
- pub fn normalize_dirs < ' p , ' h > ( file : File < ' p , ' h > ) -> FileResponse < ' p , ' h > {
356
- if !file. full_uri . path ( ) . raw ( ) . ends_with ( '/' ) && file. path . is_dir ( ) {
357
- // Known good path + '/' is a good path
358
- file. into_redirect ( |o| o. map_path ( |p| format ! ( "{p}/" ) ) . unwrap ( ) )
374
+ pub fn normalize_dirs < ' p , ' h > ( file : File < ' p , ' h > , req : & Request < ' _ > ) -> FileResponse < ' p , ' h > {
375
+ if !req. uri ( ) . path ( ) . raw ( ) . ends_with ( '/' ) && file. path . is_dir ( ) {
376
+ FileResponse :: Redirect ( Redirect :: permanent (
377
+ // Known good path + '/' is a good path
378
+ req. uri ( ) . clone ( ) . into_owned ( ) . map_path ( |p| format ! ( "{p}/" ) ) . unwrap ( )
379
+ ) )
359
380
} else {
360
381
FileResponse :: File ( file)
361
382
}
@@ -378,9 +399,9 @@ pub fn normalize_dirs<'p, 'h>(file: File<'p, 'h>) -> FileResponse<'p, 'h> {
378
399
/// # }
379
400
/// ```
380
401
pub fn index ( index : & ' static str )
381
- -> impl for <' p , ' h > Fn ( File < ' p , ' h > ) -> FileResponse < ' p , ' h > + Send + Sync
402
+ -> impl for <' p , ' h > Fn ( File < ' p , ' h > , & Request < ' _ > ) -> FileResponse < ' p , ' h > + Send + Sync
382
403
{
383
- move |f| if f. path . is_dir ( ) {
404
+ move |f, _r | if f. path . is_dir ( ) {
384
405
FileResponse :: File ( f. map_path ( |p| p. join ( index) ) )
385
406
} else {
386
407
FileResponse :: File ( f)
@@ -450,8 +471,8 @@ impl FileServer {
450
471
///
451
472
/// Redirects all requests that have been filtered to the root of the `FileServer`.
452
473
/// ```rust,no_run
453
- /// # use rocket::{fs::{FileServer, FileResponse}, response::Redirect, uri, Build, Rocket};
454
- /// fn redir_missing<'p, 'h>(p: Option<FileResponse<'p, 'h>>)
474
+ /// # use rocket::{fs::{FileServer, FileResponse}, response::Redirect, uri, Build, Rocket, Request };
475
+ /// fn redir_missing<'p, 'h>(p: Option<FileResponse<'p, 'h>>, _req: &Request<'_> )
455
476
/// -> Option<FileResponse<'p, 'h>>
456
477
/// {
457
478
/// match p {
@@ -485,12 +506,12 @@ impl FileServer {
485
506
/// .mount(
486
507
/// "/",
487
508
/// FileServer::from("static")
488
- /// .filter_file(|f| f.path.file_name() != Some("hidden".as_ref()))
509
+ /// .filter_file(|f, _r | f.path.file_name() != Some("hidden".as_ref()))
489
510
/// )
490
511
/// # }
491
512
/// ```
492
513
pub fn filter_file < F > ( self , f : F ) -> Self
493
- where F : Fn ( & File < ' _ , ' _ > ) -> bool + Send + Sync + ' static
514
+ where F : Fn ( & File < ' _ , ' _ > , & Request < ' _ > ) -> bool + Send + Sync + ' static
494
515
{
495
516
self . and_rewrite ( FilterFile ( f) )
496
517
}
@@ -506,12 +527,12 @@ impl FileServer {
506
527
/// rocket::build()
507
528
/// .mount(
508
529
/// "/",
509
- /// FileServer::from("static").map_file(|f| f.map_path(|p| p.join("hidden")).into())
530
+ /// FileServer::from("static").map_file(|f, _r | f.map_path(|p| p.join("hidden")).into())
510
531
/// )
511
532
/// # }
512
533
/// ```
513
534
pub fn map_file < F > ( self , f : F ) -> Self
514
- where F : for < ' r , ' h > Fn ( File < ' r , ' h > ) -> FileResponse < ' r , ' h > + Send + Sync + ' static
535
+ where F : for < ' r , ' h > Fn ( File < ' r , ' h > , & Request < ' _ > ) -> FileResponse < ' r , ' h > + Send + Sync + ' static
515
536
{
516
537
self . and_rewrite ( MapFile ( f) )
517
538
}
@@ -528,6 +549,8 @@ impl From<FileServer> for Vec<Route> {
528
549
}
529
550
}
530
551
552
+
553
+
531
554
#[ crate :: async_trait]
532
555
impl Handler for FileServer {
533
556
async fn handle < ' r > ( & self , req : & ' r Request < ' _ > , data : Data < ' r > ) -> Outcome < ' r > {
@@ -536,27 +559,20 @@ impl Handler for FileServer {
536
559
. and_then ( |segments| segments. to_path_buf ( true ) . ok ( ) ) ;
537
560
let mut response = path. as_ref ( ) . map ( |p| FileResponse :: File ( File {
538
561
path : Cow :: Borrowed ( p) ,
539
- full_uri : req. uri ( ) ,
540
562
headers : HeaderMap :: new ( ) ,
541
563
} ) ) ;
542
564
543
565
for rewrite in & self . rewrites {
544
- response = rewrite. rewrite ( response) ;
566
+ response = rewrite. rewrite ( response, req ) ;
545
567
}
568
+
546
569
match response {
547
- Some ( FileResponse :: File ( File { path, headers, .. } ) ) if path. is_file ( ) => {
548
- NamedFile :: open ( path) . await . respond_to ( req) . map ( |mut r| {
549
- for header in headers {
550
- r. adjoin_raw_header ( header. name . as_str ( ) . to_owned ( ) , header. value ) ;
551
- }
552
- r
553
- } ) . or_forward ( ( data, Status :: NotFound ) )
554
- } ,
570
+ Some ( FileResponse :: File ( file) ) => file. respond_to ( req, data) . await ,
555
571
Some ( FileResponse :: Redirect ( r) ) => {
556
572
r. respond_to ( req)
557
573
. or_forward ( ( data, Status :: InternalServerError ) )
558
574
} ,
559
- _ => Outcome :: forward ( data, Status :: NotFound ) ,
575
+ None => Outcome :: forward ( data, Status :: NotFound ) ,
560
576
}
561
577
}
562
578
}
0 commit comments