Skip to content

Commit 4db4afb

Browse files
committed
Use sanitized path in dir redirect
1 parent a9cfcc0 commit 4db4afb

File tree

4 files changed

+37
-12
lines changed

4 files changed

+37
-12
lines changed

src/resolve.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ pub async fn resolve_path(
7474
request_path: &str,
7575
) -> Result<ResolveResult, IoError> {
7676
let RequestedPath {
77-
mut full_path,
77+
sanitized,
7878
is_dir_request,
79-
} = RequestedPath::resolve(root, request_path);
79+
} = RequestedPath::resolve(request_path);
80+
81+
let mut full_path = root.into();
82+
full_path.extend(&sanitized);
8083

8184
let (file, metadata) = match open_with_metadata(&full_path).await {
8285
Ok(pair) => pair,

src/response_builder.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::resolve::ResolveResult;
2-
use crate::util::FileResponseBuilder;
2+
use crate::util::{FileResponseBuilder, RequestedPath};
33
use http::response::Builder as HttpResponseBuilder;
44
use http::{header, HeaderMap, Method, Request, Response, Result, StatusCode, Uri};
55
use hyper::Body;
@@ -84,7 +84,20 @@ impl<'a> ResponseBuilder<'a> {
8484
.status(StatusCode::FORBIDDEN)
8585
.body(Body::empty()),
8686
ResolveResult::IsDirectory => {
87-
let mut target = self.path.to_owned();
87+
// NOTE: We are doing an origin-relative redirect, but need to use the sanitized
88+
// path in order to prevent a malicious redirect to `//foo` (schema-relative).
89+
// With the current API, we have no other option here than to do sanitization
90+
// again, but a future version may reuse the earlier sanitization result.
91+
let resolved = RequestedPath::resolve(self.path);
92+
let path = resolved.sanitized.to_string_lossy();
93+
94+
let mut target_len = path.len() + 2;
95+
if let Some(ref query) = self.query {
96+
target_len += query.len() + 1;
97+
}
98+
let mut target = String::with_capacity(target_len);
99+
target.push('/');
100+
target.push_str(&path);
88101
target.push('/');
89102
if let Some(query) = self.query {
90103
target.push('?');

src/util/requested_path.rs

+4-8
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,19 @@ fn normalize_path(path: &Path) -> PathBuf {
3131

3232
/// Resolved request path.
3333
pub struct RequestedPath {
34-
/// Fully resolved filesystem path of the request.
35-
pub full_path: PathBuf,
34+
/// Sanitized path of the request.
35+
pub sanitized: PathBuf,
3636
/// Whether a directory was requested. (`original` ends with a slash.)
3737
pub is_dir_request: bool,
3838
}
3939

4040
impl RequestedPath {
4141
/// Resolve the requested path to a full filesystem path, limited to the root.
42-
pub fn resolve(root_path: impl Into<PathBuf>, request_path: &str) -> Self {
42+
pub fn resolve(request_path: &str) -> Self {
4343
let is_dir_request = request_path.as_bytes().last() == Some(&b'/');
4444
let request_path = PathBuf::from(decode_percents(request_path));
45-
46-
let mut full_path = root_path.into();
47-
full_path.extend(&normalize_path(&request_path));
48-
4945
RequestedPath {
50-
full_path,
46+
sanitized: normalize_path(&request_path),
5147
is_dir_request,
5248
}
5349
}

tests/static.rs

+13
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,19 @@ async fn redirects_if_trailing_slash_is_missing() {
112112
assert_eq!(url, "/dir/");
113113
}
114114

115+
#[tokio::test]
116+
async fn redirects_to_sanitized_path() {
117+
let harness = Harness::new(vec![("dir/index.html", "this is index")]);
118+
119+
// Previous versions would base the redirect on the request path, but that is user input, and
120+
// the user could construct a schema-relative redirect this way.
121+
let res = harness.get("//dir").await.unwrap();
122+
assert_eq!(res.status(), StatusCode::MOVED_PERMANENTLY);
123+
124+
let url = res.headers().get(header::LOCATION).unwrap();
125+
assert_eq!(url, "/dir/");
126+
}
127+
115128
#[tokio::test]
116129
async fn decodes_percent_notation() {
117130
let harness = Harness::new(vec![("has space.html", "file with funky chars")]);

0 commit comments

Comments
 (0)