Skip to content

Commit c0dca6e

Browse files
committed
refactor: improve dir path scan during directory listing
1 parent 920acb2 commit c0dca6e

File tree

1 file changed

+42
-25
lines changed

1 file changed

+42
-25
lines changed

src/static_files.rs

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,36 @@ pub async fn handle_request(
4040
return Err(StatusCode::METHOD_NOT_ALLOWED);
4141
}
4242

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?;
4444

4545
// Directory listing
4646
// 1. Check if "directory listing" feature is enabled,
4747
// if current path is a valid directory and
4848
// 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+
};
5660

61+
let mut resp = Response::new(Body::empty());
5762
resp.headers_mut().insert(hyper::header::LOCATION, loc);
5863
*resp.status_mut() = StatusCode::PERMANENT_REDIRECT;
64+
tracing::trace!("uri doesn't end with a slash so redirect permanently");
5965

6066
return Ok(resp);
6167
}
6268

63-
return directory_listing(method, (current_path.to_owned(), path)).await;
69+
return directory_listing(method, uri_path, &filepath).await;
6470
}
6571

66-
file_reply(headers, (path, meta, auto_index)).await
72+
file_reply(headers, (filepath, meta, auto_index)).await
6773
}
6874

6975
fn path_from_tail(
@@ -90,23 +96,27 @@ fn path_from_tail(
9096
})
9197
}
9298

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 {
98104
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);
101111

102112
tokio::fs::read_dir(parent).then(move |res| match res {
103113
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 {
105115
Ok(resp) => Ok(resp),
106116
Err(err) => {
107117
tracing::error!(
108118
"error during directory entries reading (path={:?}): {} ",
109-
path.parent().unwrap().display(),
119+
parent.display(),
110120
err
111121
);
112122
Err(StatusCode::INTERNAL_SERVER_ERROR)
@@ -116,17 +126,17 @@ fn directory_listing(
116126
Err(err) => {
117127
let status = match err.kind() {
118128
io::ErrorKind::NotFound => {
119-
tracing::debug!("entry file not found: {:?}", path.display());
129+
tracing::debug!("entry file not found: {:?}", filepath.display());
120130
StatusCode::NOT_FOUND
121131
}
122132
io::ErrorKind::PermissionDenied => {
123-
tracing::warn!("entry file permission denied: {:?}", path.display());
133+
tracing::warn!("entry file permission denied: {:?}", filepath.display());
124134
StatusCode::FORBIDDEN
125135
}
126136
_ => {
127137
tracing::error!(
128-
"directory entries error (path={:?}): {} ",
129-
path.display(),
138+
"directory entries error (filepath={:?}): {} ",
139+
filepath.display(),
130140
err
131141
);
132142
StatusCode::INTERNAL_SERVER_ERROR
@@ -147,6 +157,7 @@ async fn read_directory_entries(
147157
if base_path != "/" {
148158
entries_str = String::from(r#"<tr><td colspan="3"><a href="../">../</a></td></tr>"#);
149159
}
160+
150161
let mut dirs_count: usize = 0;
151162
let mut files_count: usize = 0;
152163
while let Some(entry) = entries.next_entry().await? {
@@ -185,15 +196,21 @@ async fn read_directory_entries(
185196
}
186197

187198
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+
};
189206

190207
entries_str = format!(
191208
"{}<tr><td><a href=\"{}\" title=\"{}\">{}</a></td><td style=\"width: 160px;\">{}</td><td align=\"right\">{}</td></tr>",
192209
entries_str,
193210
uri,
194211
name,
195212
name,
196-
modified.to_local().strftime("%F %T").unwrap(),
213+
modified,
197214
filesize_str
198215
);
199216
}

0 commit comments

Comments
 (0)