@@ -18,6 +18,7 @@ use std::io;
18
18
use std:: ops:: Bound ;
19
19
use std:: path:: PathBuf ;
20
20
use std:: pin:: Pin ;
21
+ use std:: sync:: Arc ;
21
22
use std:: task:: Poll ;
22
23
use std:: time:: { SystemTime , UNIX_EPOCH } ;
23
24
use std:: { cmp, path:: Path } ;
@@ -27,12 +28,22 @@ use tokio_util::io::poll_read_buf;
27
28
28
29
use crate :: Result ;
29
30
31
+ /// Arc `PathBuf` reference wrapper since Arc<PathBuf> doesn't implement AsRef<Path>.
32
+ #[ derive( Clone , Debug ) ]
33
+ pub struct ArcPath ( pub Arc < PathBuf > ) ;
34
+
35
+ impl AsRef < Path > for ArcPath {
36
+ fn as_ref ( & self ) -> & Path {
37
+ ( * self . 0 ) . as_ref ( )
38
+ }
39
+ }
40
+
30
41
/// Entry point to handle incoming requests which map to specific files
31
42
/// on file system and return a file response.
32
43
pub async fn handle (
33
44
method : & Method ,
34
45
headers : & HeaderMap < HeaderValue > ,
35
- base : & Path ,
46
+ path : impl Into < PathBuf > ,
36
47
uri_path : & str ,
37
48
dir_listing : bool ,
38
49
) -> Result < Response < Body > , StatusCode > {
@@ -41,13 +52,14 @@ pub async fn handle(
41
52
return Err ( StatusCode :: METHOD_NOT_ALLOWED ) ;
42
53
}
43
54
44
- let ( filepath, meta, auto_index) = path_from_tail ( base. to_owned ( ) , uri_path) . await ?;
55
+ let base = Arc :: new ( path. into ( ) ) ;
56
+ let ( filepath, meta, auto_index) = path_from_tail ( base, uri_path) . await ?;
45
57
46
58
// Directory listing
47
59
// 1. Check if "directory listing" feature is enabled,
48
60
// if current path is a valid directory and
49
61
// if it does not contain an `index.html` file
50
- if dir_listing && auto_index && !filepath. exists ( ) {
62
+ if dir_listing && auto_index && !filepath. as_ref ( ) . exists ( ) {
51
63
// Redirect if current path does not end with a slash char
52
64
if !uri_path. ends_with ( '/' ) {
53
65
let uri = [ uri_path, "/" ] . concat ( ) ;
@@ -67,19 +79,19 @@ pub async fn handle(
67
79
return Ok ( resp) ;
68
80
}
69
81
70
- return directory_listing ( method, uri_path, & filepath) . await ;
82
+ return directory_listing ( method, uri_path, filepath. as_ref ( ) ) . await ;
71
83
}
72
84
73
- file_reply ( headers, ( filepath, meta, auto_index) ) . await
85
+ file_reply ( headers, ( filepath, & meta, auto_index) ) . await
74
86
}
75
87
76
88
/// Convert an incoming uri into a valid and sanitized path then returns a tuple
77
89
// with the path as well as its file metadata and an auto index check if it's a directory.
78
90
fn path_from_tail (
79
- base : PathBuf ,
91
+ base : Arc < PathBuf > ,
80
92
tail : & str ,
81
- ) -> impl Future < Output = Result < ( PathBuf , Metadata , bool ) , StatusCode > > + Send {
82
- future:: ready ( sanitize_path ( base, tail) ) . and_then ( |mut buf| async {
93
+ ) -> impl Future < Output = Result < ( ArcPath , Metadata , bool ) , StatusCode > > + Send {
94
+ future:: ready ( sanitize_path ( base. as_ref ( ) , tail) ) . and_then ( |mut buf| async {
83
95
match tokio:: fs:: metadata ( & buf) . await {
84
96
Ok ( meta) => {
85
97
let mut auto_index = false ;
@@ -89,7 +101,7 @@ fn path_from_tail(
89
101
auto_index = true ;
90
102
}
91
103
tracing:: trace!( "dir: {:?}" , buf) ;
92
- Ok ( ( buf, meta, auto_index) )
104
+ Ok ( ( ArcPath ( Arc :: new ( buf) ) , meta, auto_index) )
93
105
}
94
106
Err ( err) => {
95
107
tracing:: debug!( "file not found: {:?}" , err) ;
@@ -273,26 +285,30 @@ fn parse_last_modified(modified: SystemTime) -> Result<time::Tm, Box<dyn std::er
273
285
}
274
286
275
287
/// Reply with a file content.
276
- fn file_reply (
277
- headers : & HeaderMap < HeaderValue > ,
278
- res : ( PathBuf , Metadata , bool ) ,
279
- ) -> impl Future < Output = Result < Response < Body > , StatusCode > > + Send {
288
+ fn file_reply < ' a > (
289
+ headers : & ' a HeaderMap < HeaderValue > ,
290
+ res : ( ArcPath , & ' a Metadata , bool ) ,
291
+ ) -> impl Future < Output = Result < Response < Body > , StatusCode > > + Send + ' a {
280
292
let ( path, meta, auto_index) = res;
281
293
let conditionals = get_conditional_headers ( headers) ;
282
294
TkFile :: open ( path. clone ( ) ) . then ( move |res| match res {
283
295
Ok ( f) => Either :: Left ( file_conditional ( f, path, meta, auto_index, conditionals) ) ,
284
296
Err ( err) => {
285
297
let status = match err. kind ( ) {
286
298
io:: ErrorKind :: NotFound => {
287
- tracing:: debug!( "file not found: {:?}" , path. display( ) ) ;
299
+ tracing:: debug!( "file not found: {:?}" , path. as_ref ( ) . display( ) ) ;
288
300
StatusCode :: NOT_FOUND
289
301
}
290
302
io:: ErrorKind :: PermissionDenied => {
291
- tracing:: warn!( "file permission denied: {:?}" , path. display( ) ) ;
303
+ tracing:: warn!( "file permission denied: {:?}" , path. as_ref ( ) . display( ) ) ;
292
304
StatusCode :: FORBIDDEN
293
305
}
294
306
_ => {
295
- tracing:: error!( "file open error (path={:?}): {} " , path. display( ) , err) ;
307
+ tracing:: error!(
308
+ "file open error (path={:?}): {} " ,
309
+ path. as_ref( ) . display( ) ,
310
+ err
311
+ ) ;
296
312
StatusCode :: INTERNAL_SERVER_ERROR
297
313
}
298
314
} ;
@@ -315,7 +331,8 @@ fn get_conditional_headers(header_list: &HeaderMap<HeaderValue>) -> Conditionals
315
331
}
316
332
}
317
333
318
- fn sanitize_path ( mut buf : PathBuf , tail : & str ) -> Result < PathBuf , StatusCode > {
334
+ fn sanitize_path ( base : impl AsRef < Path > , tail : & str ) -> Result < PathBuf , StatusCode > {
335
+ let mut buf = PathBuf :: from ( base. as_ref ( ) ) ;
319
336
let p = match percent_decode_str ( tail) . decode_utf8 ( ) {
320
337
Ok ( p) => p,
321
338
Err ( err) => {
@@ -400,38 +417,30 @@ impl Conditionals {
400
417
}
401
418
}
402
419
403
- fn file_conditional (
420
+ async fn file_conditional (
404
421
f : TkFile ,
405
- path : PathBuf ,
406
- meta : Metadata ,
422
+ path : ArcPath ,
423
+ meta : & Metadata ,
407
424
auto_index : bool ,
408
425
conditionals : Conditionals ,
409
- ) -> impl Future < Output = Result < Response < Body > , StatusCode > > + Send {
410
- file_metadata ( f, meta, auto_index)
411
- . map_ok ( |( file, meta) | response_body ( file, & meta, path, conditionals) )
412
- }
413
-
414
- async fn file_metadata (
415
- f : TkFile ,
416
- meta : Metadata ,
417
- auto_index : bool ,
418
- ) -> Result < ( TkFile , Metadata ) , StatusCode > {
426
+ ) -> Result < Response < Body > , StatusCode > {
419
427
if !auto_index {
420
- return Ok ( ( f, meta) ) ;
421
- }
422
- match f. metadata ( ) . await {
423
- Ok ( meta) => Ok ( ( f, meta) ) ,
424
- Err ( err) => {
425
- tracing:: debug!( "file metadata error: {}" , err) ;
426
- Err ( StatusCode :: INTERNAL_SERVER_ERROR )
428
+ Ok ( response_body ( f, meta, path, conditionals) )
429
+ } else {
430
+ match f. metadata ( ) . await {
431
+ Ok ( meta) => Ok ( response_body ( f, & meta, path, conditionals) ) ,
432
+ Err ( err) => {
433
+ tracing:: debug!( "file metadata error: {}" , err) ;
434
+ Err ( StatusCode :: INTERNAL_SERVER_ERROR )
435
+ }
427
436
}
428
437
}
429
438
}
430
439
431
440
fn response_body (
432
441
file : TkFile ,
433
442
meta : & Metadata ,
434
- path : PathBuf ,
443
+ path : ArcPath ,
435
444
conditionals : Conditionals ,
436
445
) -> Response < Body > {
437
446
let mut len = meta. len ( ) ;
@@ -623,14 +632,14 @@ mod tests {
623
632
}
624
633
625
634
assert_eq ! (
626
- sanitize_path( base. into ( ) , "/foo.html" ) . unwrap( ) ,
635
+ sanitize_path( base, "/foo.html" ) . unwrap( ) ,
627
636
p( "/var/www/foo.html" )
628
637
) ;
629
638
630
639
// bad paths
631
- sanitize_path ( base. into ( ) , "/../foo.html" ) . expect_err ( "dot dot" ) ;
640
+ sanitize_path ( base, "/../foo.html" ) . expect_err ( "dot dot" ) ;
632
641
633
- sanitize_path ( base. into ( ) , "/C:\\ /foo.html" ) . expect_err ( "C:\\ " ) ;
642
+ sanitize_path ( base, "/C:\\ /foo.html" ) . expect_err ( "C:\\ " ) ;
634
643
}
635
644
636
645
#[ test]
0 commit comments