Skip to content

Commit

Permalink
fix: percent decode path segments
Browse files Browse the repository at this point in the history
  • Loading branch information
ttys3 committed Aug 26, 2021
1 parent 2431e03 commit 30c4820
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 2 deletions.
1 change: 1 addition & 0 deletions tower-http/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

# Unreleased

- Fix the bug that `ServeDir` can not handle percent encoded uri path correctly.
- Fix a [bug](https://github.com/tower-rs/tower-http/issues/121) which happens when `append_index_html_on_directories` is set to `false` in `ServeDir`.

## Breaking changes
Expand Down
3 changes: 2 additions & 1 deletion tower-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ tokio = { version = "1", optional = true, default_features = false }
tokio-util = { version = "0.6", optional = true, default_features = false, features = ["io"] }
tower = { version = "0.4.1", optional = true }
tracing = { version = "0.1", default_features = false, optional = true }
percent-encoding = { version = "2.1.0", optional = true }

[dev-dependencies]
bytes = "1"
Expand Down Expand Up @@ -67,7 +68,7 @@ full = [
add-extension = []
auth = ["base64"]
follow-redirect = ["iri-string", "tower/util"]
fs = ["tokio/fs", "tokio-util/io", "mime_guess", "mime"]
fs = ["tokio/fs", "tokio-util/io", "mime_guess", "mime", "percent-encoding"]
map-request-body = []
map-response-body = []
metrics = ["tokio/time"]
Expand Down
58 changes: 57 additions & 1 deletion tower-http/src/services/fs/serve_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::{
};
use tokio::fs::File;
use tower_service::Service;
use percent_encoding::percent_decode;

/// Service that serves files from a given directory and all its sub directories.
///
Expand Down Expand Up @@ -66,8 +67,17 @@ impl<ReqBody> Service<Request<ReqBody>> for ServeDir {
// build and validate the path
let path = req.uri().path();
let path = path.trim_start_matches('/');

let path_decoded = if let Ok(decoded_utf8) = percent_decode(path.as_ref()).decode_utf8() {
decoded_utf8
} else {
return ResponseFuture {
inner: Inner::Invalid,
}
};

let mut full_path = self.base.clone();
for seg in path.split('/') {
for seg in path_decoded.split('/') {
if seg.starts_with("..") || seg.contains('\\') {
return ResponseFuture {
inner: Inner::Invalid,
Expand Down Expand Up @@ -327,4 +337,50 @@ mod tests {
let bytes = hyper::body::to_bytes(body).await.unwrap();
String::from_utf8(bytes.to_vec()).unwrap()
}

#[tokio::test]
async fn access_cjk_percent_encoded_uri_path() {
let cjk_filename = "你好世界.txt";
// percent encoding present of 你好世界.txt
let cjk_filename_encoded = "%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C.txt";

let tmp_dir = std::env::temp_dir();
let tmp_filename = std::path::Path::new(tmp_dir.as_path()).join(cjk_filename);
let _ = tokio::fs::File::create(&tmp_filename).await.unwrap();

let svc = ServeDir::new(&tmp_dir);

let req = Request::builder()
.uri(format!("/{}", cjk_filename_encoded))
.body(Body::empty())
.unwrap();
let res = svc.oneshot(req).await.unwrap();

assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.headers()["content-type"], "text/plain");
let _ = tokio::fs::remove_file(&tmp_filename).await.unwrap();
}

#[tokio::test]
async fn access_space_percent_encoded_uri_path() {
let raw_filename = "filename with space.txt";
// percent encoding present of "filename with space.txt"
let encoded_filename = "filename%20with%20space.txt";

let tmp_dir = std::env::temp_dir();
let tmp_filename = std::path::Path::new(tmp_dir.as_path()).join(raw_filename);
let _ = tokio::fs::File::create(&tmp_filename).await.unwrap();

let svc = ServeDir::new(&tmp_dir);

let req = Request::builder()
.uri(format!("/{}", encoded_filename))
.body(Body::empty())
.unwrap();
let res = svc.oneshot(req).await.unwrap();

assert_eq!(res.status(), StatusCode::OK);
assert_eq!(res.headers()["content-type"], "text/plain");
let _ = tokio::fs::remove_file(&tmp_filename).await.unwrap();
}
}

0 comments on commit 30c4820

Please sign in to comment.