@@ -40,30 +40,36 @@ pub async fn handle_request(
40
40
return Err ( StatusCode :: METHOD_NOT_ALLOWED ) ;
41
41
}
42
42
43
- let ( path , meta, auto_index) = path_from_tail ( base. into ( ) , uri_path) . await ?;
43
+ let ( filepath , meta, auto_index) = path_from_tail ( base. to_owned ( ) , uri_path) . await ?;
44
44
45
45
// Directory listing
46
46
// 1. Check if "directory listing" feature is enabled,
47
47
// if current path is a valid directory and
48
48
// if it does not contain an `index.html` file
49
- if dir_listing && auto_index && !path. exists ( ) {
50
- // Redirect if current path does not end with a slash
51
- let current_path = uri_path;
52
- if !current_path. ends_with ( '/' ) {
53
- let uri = [ current_path, "/" ] . concat ( ) ;
54
- let loc = HeaderValue :: from_str ( uri. as_str ( ) ) . unwrap ( ) ;
55
- let mut resp = Response :: new ( Body :: empty ( ) ) ;
49
+ if dir_listing && auto_index && !filepath. exists ( ) {
50
+ // Redirect if current path does not end with a slash char
51
+ if !uri_path. ends_with ( '/' ) {
52
+ let uri = [ uri_path, "/" ] . concat ( ) ;
53
+ let loc = match HeaderValue :: from_str ( uri. as_str ( ) ) {
54
+ Ok ( val) => val,
55
+ Err ( err) => {
56
+ tracing:: error!( "invalid header value from current uri: {:?}" , err) ;
57
+ return Err ( StatusCode :: INTERNAL_SERVER_ERROR ) ;
58
+ }
59
+ } ;
56
60
61
+ let mut resp = Response :: new ( Body :: empty ( ) ) ;
57
62
resp. headers_mut ( ) . insert ( hyper:: header:: LOCATION , loc) ;
58
63
* resp. status_mut ( ) = StatusCode :: PERMANENT_REDIRECT ;
64
+ tracing:: trace!( "uri doesn't end with a slash so redirect permanently" ) ;
59
65
60
66
return Ok ( resp) ;
61
67
}
62
68
63
- return directory_listing ( method, ( current_path . to_owned ( ) , path ) ) . await ;
69
+ return directory_listing ( method, uri_path , & filepath ) . await ;
64
70
}
65
71
66
- file_reply ( headers, ( path , meta, auto_index) ) . await
72
+ file_reply ( headers, ( filepath , meta, auto_index) ) . await
67
73
}
68
74
69
75
fn path_from_tail (
@@ -90,23 +96,27 @@ fn path_from_tail(
90
96
} )
91
97
}
92
98
93
- fn directory_listing (
94
- method : & Method ,
95
- res : ( String , PathBuf ) ,
96
- ) -> impl Future < Output = Result < Response < Body > , StatusCode > > + Send {
97
- let ( current_path , path ) = res ;
99
+ fn directory_listing < ' a > (
100
+ method : & ' a Method ,
101
+ current_path : & ' a str ,
102
+ filepath : & ' a Path ,
103
+ ) -> impl Future < Output = Result < Response < Body > , StatusCode > > + Send + ' a {
98
104
let is_head = method == Method :: HEAD ;
99
- let parent = path. parent ( ) . unwrap ( ) ;
100
- let parent = PathBuf :: from ( parent) ;
105
+
106
+ // Note: it's safe to call `parent()` since `filepath` value
107
+ // always maps to a file on root directory boundaries.
108
+ // See `path_from_tail()` function which sanitizes
109
+ // the requested path before to be delegated here.
110
+ let parent = filepath. parent ( ) . unwrap_or ( filepath) ;
101
111
102
112
tokio:: fs:: read_dir ( parent) . then ( move |res| match res {
103
113
Ok ( entries) => Either :: Left ( async move {
104
- match read_directory_entries ( entries, & current_path, is_head) . await {
114
+ match read_directory_entries ( entries, current_path, is_head) . await {
105
115
Ok ( resp) => Ok ( resp) ,
106
116
Err ( err) => {
107
117
tracing:: error!(
108
118
"error during directory entries reading (path={:?}): {} " ,
109
- path . parent( ) . unwrap ( ) . display( ) ,
119
+ parent. display( ) ,
110
120
err
111
121
) ;
112
122
Err ( StatusCode :: INTERNAL_SERVER_ERROR )
@@ -116,17 +126,17 @@ fn directory_listing(
116
126
Err ( err) => {
117
127
let status = match err. kind ( ) {
118
128
io:: ErrorKind :: NotFound => {
119
- tracing:: debug!( "entry file not found: {:?}" , path . display( ) ) ;
129
+ tracing:: debug!( "entry file not found: {:?}" , filepath . display( ) ) ;
120
130
StatusCode :: NOT_FOUND
121
131
}
122
132
io:: ErrorKind :: PermissionDenied => {
123
- tracing:: warn!( "entry file permission denied: {:?}" , path . display( ) ) ;
133
+ tracing:: warn!( "entry file permission denied: {:?}" , filepath . display( ) ) ;
124
134
StatusCode :: FORBIDDEN
125
135
}
126
136
_ => {
127
137
tracing:: error!(
128
- "directory entries error (path ={:?}): {} " ,
129
- path . display( ) ,
138
+ "directory entries error (filepath ={:?}): {} " ,
139
+ filepath . display( ) ,
130
140
err
131
141
) ;
132
142
StatusCode :: INTERNAL_SERVER_ERROR
@@ -147,6 +157,7 @@ async fn read_directory_entries(
147
157
if base_path != "/" {
148
158
entries_str = String :: from ( r#"<tr><td colspan="3"><a href="../">../</a></td></tr>"# ) ;
149
159
}
160
+
150
161
let mut dirs_count: usize = 0 ;
151
162
let mut files_count: usize = 0 ;
152
163
while let Some ( entry) = entries. next_entry ( ) . await ? {
@@ -185,15 +196,21 @@ async fn read_directory_entries(
185
196
}
186
197
187
198
let uri = format ! ( "{}{}" , base_path, name) ;
188
- let modified = parse_last_modified ( meta. modified ( ) ?) . unwrap ( ) ;
199
+ let modified = match parse_last_modified ( meta. modified ( ) ?) {
200
+ Ok ( tm) => tm. to_local ( ) . strftime ( "%F %T" ) ?. to_string ( ) ,
201
+ Err ( err) => {
202
+ tracing:: error!( "error determining file last modified: {:?}" , err) ;
203
+ String :: from ( "-" )
204
+ }
205
+ } ;
189
206
190
207
entries_str = format ! (
191
208
"{}<tr><td><a href=\" {}\" title=\" {}\" >{}</a></td><td style=\" width: 160px;\" >{}</td><td align=\" right\" >{}</td></tr>" ,
192
209
entries_str,
193
210
uri,
194
211
name,
195
212
name,
196
- modified. to_local ( ) . strftime ( "%F %T" ) . unwrap ( ) ,
213
+ modified,
197
214
filesize_str
198
215
) ;
199
216
}
0 commit comments