Skip to content

Commit cf3046f

Browse files
authored
ServeDir to call fallback for invalid filenames (#586)
1 parent bd397ca commit cf3046f

File tree

4 files changed

+77
-3
lines changed

4 files changed

+77
-3
lines changed

tower-http/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
## Fixed
1111

1212
- `on_eos` is now called even for successful responses. ([#580])
13+
- `ServeDir`: call fallback when filename is invalid ([#586])
1314

1415
[#580]: https://github.com/tower-rs/tower-http/pull/580
16+
[#586]: https://github.com/tower-rs/tower-http/pull/586
1517

1618
# 0.6.6
1719

tower-http/src/services/fs/serve_dir/future.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ where
105105
break Poll::Ready(Ok(res));
106106
}
107107

108-
Ok(OpenFileOutput::FileNotFound) => {
108+
Ok(OpenFileOutput::FileNotFound | OpenFileOutput::InvalidFilename) => {
109109
if let Some((mut fallback, request)) = fallback_and_request.take() {
110110
call_fallback(&mut fallback, request)
111111
} else {

tower-http/src/services/fs/serve_dir/open_file.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use http_range_header::RangeUnsatisfiableError;
1010
use std::{
1111
ffi::OsStr,
1212
fs::Metadata,
13-
io::{self, SeekFrom},
13+
io::{self, ErrorKind, SeekFrom},
1414
ops::RangeInclusive,
1515
path::{Path, PathBuf},
1616
};
@@ -23,6 +23,7 @@ pub(super) enum OpenFileOutput {
2323
PreconditionFailed,
2424
NotModified,
2525
InvalidRedirectUri,
26+
InvalidFilename,
2627
}
2728

2829
pub(super) struct FileOpened {
@@ -110,7 +111,15 @@ pub(super) async fn open_file(
110111
})))
111112
} else {
112113
let (mut file, maybe_encoding) =
113-
open_file_with_fallback(path_to_file, negotiated_encodings).await?;
114+
match open_file_with_fallback(path_to_file, negotiated_encodings).await {
115+
Ok(result) => result,
116+
117+
Err(err) if is_invalid_filename_error(&err) => {
118+
return Ok(OpenFileOutput::InvalidFilename)
119+
}
120+
Err(err) => return Err(err),
121+
};
122+
114123
let meta = file.metadata().await?;
115124
let last_modified = meta.modified().ok().map(LastModified::from);
116125
if let Some(output) = check_modified_headers(
@@ -141,6 +150,26 @@ pub(super) async fn open_file(
141150
}
142151
}
143152

153+
fn is_invalid_filename_error(err: &io::Error) -> bool {
154+
// Only applies to NULL bytes
155+
if err.kind() == ErrorKind::InvalidInput {
156+
return true;
157+
}
158+
159+
// FIXME: Remove when MSRV >= 1.87.
160+
// `io::ErrorKind::InvalidFilename` is stabilized in v1.87
161+
#[cfg(windows)]
162+
if let Some(raw_err) = err.raw_os_error() {
163+
// https://github.com/rust-lang/rust/blob/70e2b4a4d197f154bed0eb3dcb5cac6a948ff3a3/library/std/src/sys/pal/windows/mod.rs
164+
// Lines 81 and 115
165+
if (raw_err == 123) || (raw_err == 161) || (raw_err == 206) {
166+
return true;
167+
}
168+
}
169+
170+
false
171+
}
172+
144173
fn check_modified_headers(
145174
modified: Option<&LastModified>,
146175
if_unmodified_since: Option<IfUnmodifiedSince>,

tower-http/src/services/fs/serve_dir/tests.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,3 +834,46 @@ async fn calls_fallback_on_invalid_paths() {
834834

835835
assert_eq!(res.headers()["from-fallback"], "1");
836836
}
837+
838+
// https://github.com/tower-rs/tower-http/issues/573
839+
#[tokio::test]
840+
async fn calls_fallback_on_invalid_filenames() {
841+
async fn fallback<T>(_: T) -> Result<Response<Body>, Infallible> {
842+
let mut res = Response::new(Body::empty());
843+
res.headers_mut()
844+
.insert("from-fallback", "1".parse().unwrap());
845+
Ok(res)
846+
}
847+
848+
let svc = ServeDir::new("..").fallback(service_fn(fallback));
849+
850+
let req = Request::builder()
851+
.uri("/invalid|path")
852+
.body(Body::empty())
853+
.unwrap();
854+
855+
let res = svc.oneshot(req).await.unwrap();
856+
857+
assert_eq!(res.headers()["from-fallback"], "1");
858+
}
859+
860+
#[tokio::test]
861+
async fn calls_fallback_on_null() {
862+
async fn fallback<T>(_: T) -> Result<Response<Body>, Infallible> {
863+
let mut res = Response::new(Body::empty());
864+
res.headers_mut()
865+
.insert("from-fallback", "1".parse().unwrap());
866+
Ok(res)
867+
}
868+
869+
let svc = ServeDir::new("..").fallback(service_fn(fallback));
870+
871+
let req = Request::builder()
872+
.uri("/invalid-path%00")
873+
.body(Body::empty())
874+
.unwrap();
875+
876+
let res = svc.oneshot(req).await.unwrap();
877+
878+
assert_eq!(res.headers()["from-fallback"], "1");
879+
}

0 commit comments

Comments
 (0)