diff --git a/test-files/extensionless_precompressed b/test-files/extensionless_precompressed new file mode 100644 index 00000000..fffc22b4 --- /dev/null +++ b/test-files/extensionless_precompressed @@ -0,0 +1 @@ +Content. \ No newline at end of file diff --git a/test-files/extensionless_precompressed.gz b/test-files/extensionless_precompressed.gz new file mode 100644 index 00000000..0dcede22 Binary files /dev/null and b/test-files/extensionless_precompressed.gz differ diff --git a/test-files/extensionless_precompressed_missing b/test-files/extensionless_precompressed_missing new file mode 100644 index 00000000..fffc22b4 --- /dev/null +++ b/test-files/extensionless_precompressed_missing @@ -0,0 +1 @@ +Content. \ No newline at end of file diff --git a/tower-http/CHANGELOG.md b/tower-http/CHANGELOG.md index e4da30fe..542f07b5 100644 --- a/tower-http/CHANGELOG.md +++ b/tower-http/CHANGELOG.md @@ -12,8 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `body` module is disabled except for `catch-panic`, `decompression-*`, `fs`, or `limit` features (BREAKING) ([#477]) - Update to `tower` 0.5 ([#503]) +## Fixed + +- **fs:** Precompression of static files now supports files without a file extension ([#507]) + [#477]: https://github.com/tower-rs/tower-http/pull/477 [#503]: https://github.com/tower-rs/tower-http/pull/503 +[#507]: https://github.com/tower-rs/tower-http/pull/507 # 0.5.2 diff --git a/tower-http/src/content_encoding.rs b/tower-http/src/content_encoding.rs index a0e60a3c..91c21d45 100644 --- a/tower-http/src/content_encoding.rs +++ b/tower-http/src/content_encoding.rs @@ -142,7 +142,7 @@ pub(crate) struct QValue(u16); ))] impl QValue { #[inline] - fn one() -> Self { + pub(crate) fn one() -> Self { Self(1000) } diff --git a/tower-http/src/services/fs/serve_dir/open_file.rs b/tower-http/src/services/fs/serve_dir/open_file.rs index 01c1e2f9..f182d422 100644 --- a/tower-http/src/services/fs/serve_dir/open_file.rs +++ b/tower-http/src/services/fs/serve_dir/open_file.rs @@ -181,16 +181,16 @@ fn preferred_encoding( if let Some(file_extension) = preferred_encoding.and_then(|encoding| encoding.to_file_extension()) { - let new_extension = path - .extension() - .map(|extension| { - let mut os_string = extension.to_os_string(); + let new_file_name = path + .file_name() + .map(|file_name| { + let mut os_string = file_name.to_os_string(); os_string.push(file_extension); os_string }) .unwrap_or_else(|| file_extension.to_os_string()); - path.set_extension(new_extension); + path.set_file_name(new_file_name); } preferred_encoding @@ -319,3 +319,17 @@ fn append_slash_on_path(uri: Uri) -> Uri { uri_builder.build().unwrap() } + +#[test] +fn preferred_encoding_with_extension() { + let mut path = PathBuf::from("hello.txt"); + preferred_encoding(&mut path, &[(Encoding::Gzip, QValue::one())]); + assert_eq!(path, PathBuf::from("hello.txt.gz")); +} + +#[test] +fn preferred_encoding_without_extension() { + let mut path = PathBuf::from("hello"); + preferred_encoding(&mut path, &[(Encoding::Gzip, QValue::one())]); + assert_eq!(path, PathBuf::from("hello.gz")); +} diff --git a/tower-http/src/services/fs/serve_dir/tests.rs b/tower-http/src/services/fs/serve_dir/tests.rs index d0d3952c..1fd768c2 100644 --- a/tower-http/src/services/fs/serve_dir/tests.rs +++ b/tower-http/src/services/fs/serve_dir/tests.rs @@ -9,6 +9,7 @@ use http::{Request, StatusCode}; use http_body::Body as HttpBody; use http_body_util::BodyExt; use std::convert::Infallible; +use std::fs; use std::io::Read; use tower::{service_fn, ServiceExt}; @@ -252,6 +253,54 @@ async fn missing_precompressed_variant_fallbacks_to_uncompressed_for_head_reques assert!(res.into_body().frame().await.is_none()); } +#[tokio::test] +async fn precompressed_without_extension() { + let svc = ServeDir::new("../test-files").precompressed_gzip(); + + let request = Request::builder() + .uri("/extensionless_precompressed") + .header("Accept-Encoding", "gzip") + .body(Body::empty()) + .unwrap(); + let res = svc.oneshot(request).await.unwrap(); + + assert_eq!(res.status(), StatusCode::OK); + + assert_eq!(res.headers()["content-type"], "application/octet-stream"); + assert_eq!(res.headers()["content-encoding"], "gzip"); + + let body = res.into_body().collect().await.unwrap().to_bytes(); + let mut decoder = GzDecoder::new(&body[..]); + let mut decompressed = String::new(); + decoder.read_to_string(&mut decompressed).unwrap(); + + let correct = fs::read_to_string("../test-files/extensionless_precompressed").unwrap(); + assert_eq!(decompressed, correct); +} + +#[tokio::test] +async fn missing_precompressed_without_extension_fallbacks_to_uncompressed() { + let svc = ServeDir::new("../test-files").precompressed_gzip(); + + let request = Request::builder() + .uri("/extensionless_precompressed_missing") + .header("Accept-Encoding", "gzip") + .body(Body::empty()) + .unwrap(); + let res = svc.oneshot(request).await.unwrap(); + + assert_eq!(res.status(), StatusCode::OK); + + assert_eq!(res.headers()["content-type"], "application/octet-stream"); + assert!(res.headers().get("content-encoding").is_none()); + + let body = res.into_body().collect().await.unwrap().to_bytes(); + let body = String::from_utf8(body.to_vec()).unwrap(); + + let correct = fs::read_to_string("../test-files/extensionless_precompressed_missing").unwrap(); + assert_eq!(body, correct); +} + #[tokio::test] async fn access_to_sub_dirs() { let svc = ServeDir::new("..");