From f38408e6527852277d9783ed0ead83fe38a3f1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8D=92=E9=87=8E=E7=84=A1=E7=87=88?= Date: Fri, 27 Aug 2021 00:59:57 +0800 Subject: [PATCH] fix: percent decode path segments --- tower-http/Cargo.toml | 3 +- tower-http/src/services/fs/serve_dir.rs | 58 ++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/tower-http/Cargo.toml b/tower-http/Cargo.toml index dae44ee7..d7f1d598 100644 --- a/tower-http/Cargo.toml +++ b/tower-http/Cargo.toml @@ -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" @@ -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"] diff --git a/tower-http/src/services/fs/serve_dir.rs b/tower-http/src/services/fs/serve_dir.rs index acffa5c7..8d621665 100644 --- a/tower-http/src/services/fs/serve_dir.rs +++ b/tower-http/src/services/fs/serve_dir.rs @@ -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. /// @@ -66,8 +67,17 @@ impl Service> 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, @@ -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(); + } }