From 2196aeac0abc6486ca4bd844c97bbe8ca1b3bc47 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Thu, 28 May 2026 15:33:25 +0900 Subject: [PATCH] fix(files): fix panic in `Files` containing `.` (#4083) --- actix-files/CHANGES.md | 2 ++ actix-files/src/lib.rs | 15 +++++++++++++++ actix-files/src/path_buf.rs | 9 ++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index aea6fe8f4..12c785192 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -5,6 +5,7 @@ - Add support for passing multiple root directories to `Files::new`. [#3402] - Add `Files::try_compressed()` to support serving pre-compressed static files [#2615] - Fix handling of `bytes=0-` +- Fix panic in `Files` when `use_hidden_files()` is enabled and request paths contain `.` segments. [#4082] - Fix `NamedFile` panic when serving files with pre-UNIX epoch modification times. [#2748] - Fix invalid `Content-Encoding: identity` header in `NamedFile` range responses. [#3191] - Update `v_htmlescape` dependency to `0.17`. @@ -13,6 +14,7 @@ [#2615]: https://github.com/actix/actix-web/pull/2615 [#2748]: https://github.com/actix/actix-web/issues/2748 [#3191]: https://github.com/actix/actix-web/issues/3191 +[#4082]: https://github.com/actix/actix-web/issues/4082 ## 0.6.10 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 3312a621d..8646da1ea 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -1094,6 +1094,21 @@ mod tests { assert_eq!(bytes, web::Bytes::from_static(b"default content")); } + #[actix_rt::test] + async fn test_hidden_files_reject_cur_dir_segment() { + let service = Files::new("/", Vec::::new()) + .use_hidden_files() + .default_handler(Files::new("/", ".").use_hidden_files()) + .new_service(()) + .await + .unwrap(); + + let req = TestRequest::with_uri("/./Cargo.toml").to_srv_request(); + let resp = test::call_service(&service, req).await; + + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + #[actix_rt::test] async fn test_serve_index_nested() { let service = Files::new(".", ".") diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index f12d68593..58b06d70f 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -78,7 +78,9 @@ impl PathBufWrap { } for segment in path.split('/') { - if segment == ".." { + if segment == "." { + return Err(UriSegmentError::BadStart('.')); + } else if segment == ".." { segment_count -= 1; buf.pop(); } else if !hidden_files && segment.starts_with('.') { @@ -180,6 +182,11 @@ mod tests { PathBufWrap::parse_path("/test/.tt", true).unwrap().0, PathBuf::from_iter(vec!["test", ".tt"]) ); + + assert_eq!( + PathBufWrap::parse_path("/test/./file.txt", true).map(|t| t.0), + Err(UriSegmentError::BadStart('.')) + ); } #[test]