From cf9c6bebd025f7a3ca81a22e21d8b19a799783d3 Mon Sep 17 00:00:00 2001 From: Blair Noctis Date: Mon, 20 Jan 2025 15:50:54 +0000 Subject: [PATCH] Constantify serve_{dir,file} test file paths and content Mainly a QoL change for downstream packagers, but might also prove useful for e.g. future refactors. Currently we have to patch all those file paths to something exsistent, due to test-files/ not being included in crates.io releases, while the Debian Rust Team pulls Rust crate source from there: https://salsa.debian.org/rust-team/debcargo-conf/-/blob/ee75c0c18a/src/tower-http/debian/patches/missing-testdata With these constants we could change only them. Test files are also changed to the same content, so one constant fits all. --- test-files/missing_precompressed.txt | 2 +- test-files/only_gzipped.txt.gz | Bin 57 -> 47 bytes test-files/precompressed.txt | 2 +- test-files/precompressed.txt.br | Bin 27 -> 15 bytes test-files/precompressed.txt.gz | Bin 59 -> 39 bytes test-files/precompressed.txt.zst | Bin 36 -> 23 bytes test-files/precompressed.txt.zz | Bin 23 -> 18 bytes tower-http/src/services/fs/serve_dir/tests.rs | 221 ++++++++++-------- tower-http/src/services/fs/serve_file.rs | 119 +++++----- 9 files changed, 185 insertions(+), 159 deletions(-) diff --git a/test-files/missing_precompressed.txt b/test-files/missing_precompressed.txt index 1cbaf90b..524acfff 100644 --- a/test-files/missing_precompressed.txt +++ b/test-files/missing_precompressed.txt @@ -1 +1 @@ -Test file! +Test file diff --git a/test-files/only_gzipped.txt.gz b/test-files/only_gzipped.txt.gz index da92e795c7f63f1c4c3059976084ebedea717a26..1865d35ad780589af98b6e88f2b59ba6074a717a 100644 GIT binary patch literal 47 zcmb2|=HSrD>`P~0&d4{tn3=9Bf Cs18s7 literal 57 zcmb2|=HReV>`P=|&d0rc|s?QVUbJgq$`hvTDP2;6QZie NP<7*qr5FPP0{}jD6rKP8 diff --git a/test-files/precompressed.txt b/test-files/precompressed.txt index e073b8dc..524acfff 100644 --- a/test-files/precompressed.txt +++ b/test-files/precompressed.txt @@ -1 +1 @@ -"This is a test file!" +Test file diff --git a/test-files/precompressed.txt.br b/test-files/precompressed.txt.br index ca313e7855530a4ecde1f4f7ca5bb048082072a2..28dc1e9d10298097f6f0fee6d3317f4e2b93acad 100644 GIT binary patch literal 15 WcmY#XVPFYKEiO?=%gjmTVg>*krUSPC literal 27 gcmd<)Zcqxz$ShU>qC|y~)Z!9_w9K4TMI|m~0B^+yz5oCK diff --git a/test-files/precompressed.txt.gz b/test-files/precompressed.txt.gz index f8913e2140d3f59ece0c99182183eb8ea9f6cb95..910dfdfbcde789f4d3a21ccc69fc76b8faeb48b5 100644 GIT binary patch literal 39 ucmb2|=HM{R>`P~0j;OPB$gwOmW#IPI)eH1Eea7=C69eC5>4{tn3=9C&a|%@e literal 59 zcmb2|=HNJUwL6i4xu7UDIX@Rj78j?c=#^BIFa&d*JfRcDu*ju#(v?R-ty|8_32{}` OW0)1CdRLr*fdK%jWELa< diff --git a/test-files/precompressed.txt.zst b/test-files/precompressed.txt.zst index 813fc9557c066ce0ab9c61d4fdead4f6da8b8121..e22efdf3f9b3b6eaf514f6b21f509293d97b9e64 100644 GIT binary patch literal 23 ecmdPcs{dDoE0BR9B(=CiAuTf}m8;-W(Psc##R$3p literal 36 qcmdPcs{dC-d?y2gQbue!DwL!amnfuV=A!d4>gj%c8TdIt%^ diff --git a/tower-http/src/services/fs/serve_dir/tests.rs b/tower-http/src/services/fs/serve_dir/tests.rs index 1fd768c2..92b05c35 100644 --- a/tower-http/src/services/fs/serve_dir/tests.rs +++ b/tower-http/src/services/fs/serve_dir/tests.rs @@ -13,28 +13,42 @@ use std::fs; use std::io::Read; use tower::{service_fn, ServiceExt}; +const H_CT: &str = "content-type"; +const H_CT_MD: &str = "text/markdown"; +const H_CT_TXT: &str = "text/plain"; +const SERVE_DIR: &str = ".."; +const SERVE_DIR_FILE: &str = "/README.md"; +const SERVE_FILE: &str = "../README.md"; +const SERVE_DIR_TF: &str = "../test-files"; +const SERVE_DIR_TF_FILE: &str = "/precompressed.txt"; +const SERVE_DIR_TF_FILE_START: &str = "Test file"; +const SERVE_DIR_TF_MISSING_PRECOMP: &str = "/missing_precompressed.txt"; +const SERVE_DIR_TF_ONLY_GZIP: &str = "/only_gzipped.txt"; +const SERVE_DIR_TF_EXTL_PRECOMP: &str = "/extensionless_precompressed"; +const SERVE_DIR_TF_EXTL_PRECOMP_MISSING: &str = "/extensionless_precompressed_missing"; + #[tokio::test] async fn basic() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "text/markdown"); + assert_eq!(res.headers()[H_CT], H_CT_MD); let body = body_into_text(res.into_body()).await; - let contents = std::fs::read_to_string("../README.md").unwrap(); + let contents = std::fs::read_to_string(SERVE_FILE).unwrap(); assert_eq!(body, contents); } #[tokio::test] async fn basic_with_index() { - let svc = ServeDir::new("../test-files"); + let svc = ServeDir::new(SERVE_DIR_TF); let req = Request::new(Body::empty()); let res = svc.oneshot(req).await.unwrap(); @@ -48,147 +62,147 @@ async fn basic_with_index() { #[tokio::test] async fn head_request() { - let svc = ServeDir::new("../test-files"); + let svc = ServeDir::new(SERVE_DIR_TF); let req = Request::builder() - .uri("/precompressed.txt") + .uri(SERVE_DIR_TF_FILE) .method(Method::HEAD) .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); - assert_eq!(res.headers()["content-length"], "23"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); + assert_eq!(res.headers()["content-length"], "10"); assert!(res.into_body().frame().await.is_none()); } #[tokio::test] async fn precompresed_head_request() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let req = Request::builder() - .uri("/precompressed.txt") + .uri(SERVE_DIR_TF_FILE) .header("Accept-Encoding", "gzip") .method(Method::HEAD) .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "gzip"); - assert_eq!(res.headers()["content-length"], "59"); + assert_eq!(res.headers()["content-length"], "39"); assert!(res.into_body().frame().await.is_none()); } #[tokio::test] async fn with_custom_chunk_size() { - let svc = ServeDir::new("..").with_buf_chunk_size(1024 * 32); + let svc = ServeDir::new(SERVE_DIR).with_buf_chunk_size(1024 * 32); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "text/markdown"); + assert_eq!(res.headers()[H_CT], H_CT_MD); let body = body_into_text(res.into_body()).await; - let contents = std::fs::read_to_string("../README.md").unwrap(); + let contents = std::fs::read_to_string(SERVE_FILE).unwrap(); assert_eq!(body, contents); } #[tokio::test] async fn precompressed_gzip() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let req = Request::builder() - .uri("/precompressed.txt") + .uri(SERVE_DIR_TF_FILE) .header("Accept-Encoding", "gzip") .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); 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(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_DIR_TF_FILE_START)); } #[tokio::test] async fn precompressed_br() { - let svc = ServeDir::new("../test-files").precompressed_br(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_br(); let req = Request::builder() - .uri("/precompressed.txt") + .uri(SERVE_DIR_TF_FILE) .header("Accept-Encoding", "br") .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "br"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decompressed = Vec::new(); BrotliDecompress(&mut &body[..], &mut decompressed).unwrap(); let decompressed = String::from_utf8(decompressed.to_vec()).unwrap(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_DIR_TF_FILE_START)); } #[tokio::test] async fn precompressed_deflate() { - let svc = ServeDir::new("../test-files").precompressed_deflate(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_deflate(); let request = Request::builder() - .uri("/precompressed.txt") + .uri(SERVE_DIR_TF_FILE) .header("Accept-Encoding", "deflate,br") .body(Body::empty()) .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "deflate"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decoder = DeflateDecoder::new(&body[..]); let mut decompressed = String::new(); decoder.read_to_string(&mut decompressed).unwrap(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_DIR_TF_FILE_START)); } #[tokio::test] async fn unsupported_precompression_alogrithm_fallbacks_to_uncompressed() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let request = Request::builder() - .uri("/precompressed.txt") + .uri(SERVE_DIR_TF_FILE) .header("Accept-Encoding", "br") .body(Body::empty()) .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); 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(); - assert!(body.starts_with("\"This is a test file!\"")); + assert!(body.starts_with(SERVE_DIR_TF_FILE_START)); } #[tokio::test] async fn only_precompressed_variant_existing() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let request = Request::builder() - .uri("/only_gzipped.txt") + .uri(SERVE_DIR_TF_ONLY_GZIP) .body(Body::empty()) .unwrap(); let res = svc.clone().oneshot(request).await.unwrap(); @@ -197,56 +211,56 @@ async fn only_precompressed_variant_existing() { // Should reply with gzipped file if client supports it let request = Request::builder() - .uri("/only_gzipped.txt") + .uri(SERVE_DIR_TF_ONLY_GZIP) .header("Accept-Encoding", "gzip") .body(Body::empty()) .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); 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(); - assert!(decompressed.starts_with("\"This is a test file\"")); + assert!(decompressed.starts_with(SERVE_DIR_TF_FILE_START)); } #[tokio::test] async fn missing_precompressed_variant_fallbacks_to_uncompressed() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let request = Request::builder() - .uri("/missing_precompressed.txt") + .uri(SERVE_DIR_TF_MISSING_PRECOMP) .header("Accept-Encoding", "gzip") .body(Body::empty()) .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); // Uncompressed file is served because compressed version is missing 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(); - assert!(body.starts_with("Test file!")); + assert!(body.starts_with(SERVE_DIR_TF_FILE_START)); } #[tokio::test] async fn missing_precompressed_variant_fallbacks_to_uncompressed_for_head_request() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let request = Request::builder() - .uri("/missing_precompressed.txt") + .uri(SERVE_DIR_TF_MISSING_PRECOMP) .header("Accept-Encoding", "gzip") .method(Method::HEAD) .body(Body::empty()) .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); - assert_eq!(res.headers()["content-length"], "11"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); + assert_eq!(res.headers()["content-length"], "10"); // Uncompressed file is served because compressed version is missing assert!(res.headers().get("content-encoding").is_none()); @@ -255,10 +269,10 @@ async fn missing_precompressed_variant_fallbacks_to_uncompressed_for_head_reques #[tokio::test] async fn precompressed_without_extension() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let request = Request::builder() - .uri("/extensionless_precompressed") + .uri(SERVE_DIR_TF_EXTL_PRECOMP) .header("Accept-Encoding", "gzip") .body(Body::empty()) .unwrap(); @@ -266,7 +280,7 @@ async fn precompressed_without_extension() { assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "application/octet-stream"); + assert_eq!(res.headers()[H_CT], "application/octet-stream"); assert_eq!(res.headers()["content-encoding"], "gzip"); let body = res.into_body().collect().await.unwrap().to_bytes(); @@ -274,16 +288,16 @@ async fn precompressed_without_extension() { let mut decompressed = String::new(); decoder.read_to_string(&mut decompressed).unwrap(); - let correct = fs::read_to_string("../test-files/extensionless_precompressed").unwrap(); + let correct = fs::read_to_string(format!("{SERVE_DIR_TF}{SERVE_DIR_TF_EXTL_PRECOMP}")).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 svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let request = Request::builder() - .uri("/extensionless_precompressed_missing") + .uri(SERVE_DIR_TF_EXTL_PRECOMP_MISSING) .header("Accept-Encoding", "gzip") .body(Body::empty()) .unwrap(); @@ -291,19 +305,20 @@ async fn missing_precompressed_without_extension_fallbacks_to_uncompressed() { assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "application/octet-stream"); + assert_eq!(res.headers()[H_CT], "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(); + let correct = + fs::read_to_string(format!("{SERVE_DIR_TF}{SERVE_DIR_TF_EXTL_PRECOMP_MISSING}")).unwrap(); assert_eq!(body, correct); } #[tokio::test] async fn access_to_sub_dirs() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() .uri("/tower-http/Cargo.toml") @@ -312,7 +327,7 @@ async fn access_to_sub_dirs() { let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "text/x-toml"); + assert_eq!(res.headers()[H_CT], "text/x-toml"); let body = body_into_text(res.into_body()).await; @@ -322,7 +337,7 @@ async fn access_to_sub_dirs() { #[tokio::test] async fn not_found() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() .uri("/not-found") @@ -340,7 +355,7 @@ async fn not_found() { #[cfg(unix)] #[tokio::test] async fn not_found_when_not_a_directory() { - let svc = ServeDir::new("../test-files"); + let svc = ServeDir::new(SERVE_DIR_TF); // `index.html` is a file, and we are trying to request // it as a directory. @@ -360,7 +375,7 @@ async fn not_found_when_not_a_directory() { #[tokio::test] async fn not_found_precompressed() { - let svc = ServeDir::new("../test-files").precompressed_gzip(); + let svc = ServeDir::new(SERVE_DIR_TF).precompressed_gzip(); let req = Request::builder() .uri("/not-found") @@ -378,7 +393,7 @@ async fn not_found_precompressed() { #[tokio::test] async fn fallbacks_to_different_precompressed_variant_if_not_found_for_head_request() { - let svc = ServeDir::new("../test-files") + let svc = ServeDir::new(SERVE_DIR_TF) .precompressed_gzip() .precompressed_br(); @@ -390,7 +405,7 @@ async fn fallbacks_to_different_precompressed_variant_if_not_found_for_head_requ .unwrap(); let res = svc.oneshot(req).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "br"); assert_eq!(res.headers()["content-length"], "15"); @@ -399,7 +414,7 @@ async fn fallbacks_to_different_precompressed_variant_if_not_found_for_head_requ #[tokio::test] async fn fallbacks_to_different_precompressed_variant_if_not_found() { - let svc = ServeDir::new("../test-files") + let svc = ServeDir::new(SERVE_DIR_TF) .precompressed_gzip() .precompressed_br(); @@ -410,14 +425,14 @@ async fn fallbacks_to_different_precompressed_variant_if_not_found() { .unwrap(); let res = svc.oneshot(req).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "br"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decompressed = Vec::new(); BrotliDecompress(&mut &body[..], &mut decompressed).unwrap(); let decompressed = String::from_utf8(decompressed.to_vec()).unwrap(); - assert!(decompressed.starts_with("Test file")); + assert!(decompressed.starts_with(SERVE_DIR_TF_FILE_START)); } #[tokio::test] @@ -449,7 +464,7 @@ async fn empty_directory_without_index() { #[tokio::test] async fn empty_directory_without_index_no_information_leak() { - let svc = ServeDir::new("..").append_index_html_on_directories(false); + let svc = ServeDir::new(SERVE_DIR).append_index_html_on_directories(false); let req = Request::builder() .uri("/test-files") @@ -478,7 +493,7 @@ async fn access_cjk_percent_encoded_uri_path() { // percent encoding present of 你好世界.txt let cjk_filename_encoded = "%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C.txt"; - let svc = ServeDir::new("../test-files"); + let svc = ServeDir::new(SERVE_DIR_TF); let req = Request::builder() .uri(format!("/{}", cjk_filename_encoded)) @@ -487,14 +502,14 @@ async fn access_cjk_percent_encoded_uri_path() { let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); } #[tokio::test] async fn access_space_percent_encoded_uri_path() { let encoded_filename = "filename%20with%20space.txt"; - let svc = ServeDir::new("../test-files"); + let svc = ServeDir::new(SERVE_DIR_TF); let req = Request::builder() .uri(format!("/{}", encoded_filename)) @@ -503,17 +518,17 @@ async fn access_space_percent_encoded_uri_path() { let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); } #[tokio::test] async fn read_partial_in_bounds() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let bytes_start_incl = 9; let bytes_end_incl = 1023; let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header( "Range", format!("bytes={}-{}", bytes_start_incl, bytes_end_incl), @@ -522,7 +537,7 @@ async fn read_partial_in_bounds() { .unwrap(); let res = svc.oneshot(req).await.unwrap(); - let file_contents = std::fs::read("../README.md").unwrap(); + let file_contents = std::fs::read(SERVE_FILE).unwrap(); assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); assert_eq!( res.headers()["content-length"], @@ -537,7 +552,7 @@ async fn read_partial_in_bounds() { bytes_end_incl, file_contents.len() ))); - assert_eq!(res.headers()["content-type"], "text/markdown"); + assert_eq!(res.headers()[H_CT], H_CT_MD); let body = to_bytes(res.into_body()).await.ok().unwrap(); let source = Bytes::from(file_contents[bytes_start_incl..=bytes_end_incl].to_vec()); @@ -546,13 +561,13 @@ async fn read_partial_in_bounds() { #[tokio::test] async fn read_partial_accepts_out_of_bounds_range() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let bytes_start_incl = 0; let bytes_end_excl = 9999999; let requested_len = bytes_end_excl - bytes_start_incl; let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header( "Range", format!("bytes={}-{}", bytes_start_incl, requested_len - 1), @@ -562,7 +577,7 @@ async fn read_partial_accepts_out_of_bounds_range() { let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); - let file_contents = std::fs::read("../README.md").unwrap(); + let file_contents = std::fs::read(SERVE_FILE).unwrap(); // Out of bounds range gives all bytes assert_eq!( res.headers()["content-range"], @@ -576,15 +591,15 @@ async fn read_partial_accepts_out_of_bounds_range() { #[tokio::test] async fn read_partial_errs_on_garbage_header() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header("Range", "bad_format") .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); - let file_contents = std::fs::read("../README.md").unwrap(); + let file_contents = std::fs::read(SERVE_FILE).unwrap(); assert_eq!( res.headers()["content-range"], &format!("bytes */{}", file_contents.len()) @@ -593,15 +608,15 @@ async fn read_partial_errs_on_garbage_header() { #[tokio::test] async fn read_partial_errs_on_bad_range() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header("Range", "bytes=-1-15") .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::RANGE_NOT_SATISFIABLE); - let file_contents = std::fs::read("../README.md").unwrap(); + let file_contents = std::fs::read(SERVE_FILE).unwrap(); assert_eq!( res.headers()["content-range"], &format!("bytes */{}", file_contents.len()) @@ -610,9 +625,9 @@ async fn read_partial_errs_on_bad_range() { #[tokio::test] async fn accept_encoding_identity() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header("Accept-Encoding", "identity") .body(Body::empty()) .unwrap(); @@ -624,9 +639,9 @@ async fn accept_encoding_identity() { #[tokio::test] async fn last_modified() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); @@ -639,9 +654,9 @@ async fn last_modified() { // -- If-Modified-Since - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header(header::IF_MODIFIED_SINCE, last_modified) .body(Body::empty()) .unwrap(); @@ -650,9 +665,9 @@ async fn last_modified() { assert_eq!(res.status(), StatusCode::NOT_MODIFIED); assert!(res.into_body().frame().await.is_none()); - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header(header::IF_MODIFIED_SINCE, "Fri, 09 Aug 1996 14:21:40 GMT") .body(Body::empty()) .unwrap(); @@ -665,9 +680,9 @@ async fn last_modified() { // -- If-Unmodified-Since - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header(header::IF_UNMODIFIED_SINCE, last_modified) .body(Body::empty()) .unwrap(); @@ -677,9 +692,9 @@ async fn last_modified() { let body = res.into_body().collect().await.unwrap().to_bytes(); assert_eq!(body.as_ref(), readme_bytes); - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() - .uri("/README.md") + .uri(SERVE_DIR_FILE) .header(header::IF_UNMODIFIED_SINCE, "Fri, 09 Aug 1996 14:21:40 GMT") .body(Body::empty()) .unwrap(); @@ -698,7 +713,7 @@ async fn with_fallback_svc() { )))) } - let svc = ServeDir::new("..").fallback(tower::service_fn(fallback)); + let svc = ServeDir::new(SERVE_DIR).fallback(tower::service_fn(fallback)); let req = Request::builder() .uri("/doesnt-exist") @@ -714,7 +729,7 @@ async fn with_fallback_svc() { #[tokio::test] async fn with_fallback_serve_file() { - let svc = ServeDir::new("..").fallback(ServeFile::new("../README.md")); + let svc = ServeDir::new(SERVE_DIR).fallback(ServeFile::new(SERVE_FILE)); let req = Request::builder() .uri("/doesnt-exist") @@ -723,21 +738,21 @@ async fn with_fallback_serve_file() { let res = svc.oneshot(req).await.unwrap(); assert_eq!(res.status(), StatusCode::OK); - assert_eq!(res.headers()["content-type"], "text/markdown"); + assert_eq!(res.headers()[H_CT], H_CT_MD); let body = body_into_text(res.into_body()).await; - let contents = std::fs::read_to_string("../README.md").unwrap(); + let contents = std::fs::read_to_string(SERVE_FILE).unwrap(); assert_eq!(body, contents); } #[tokio::test] async fn method_not_allowed() { - let svc = ServeDir::new(".."); + let svc = ServeDir::new(SERVE_DIR); let req = Request::builder() .method(Method::POST) - .uri("/README.md") + .uri(SERVE_DIR_FILE) .body(Body::empty()) .unwrap(); let res = svc.oneshot(req).await.unwrap(); @@ -755,7 +770,7 @@ async fn calling_fallback_on_not_allowed() { )))) } - let svc = ServeDir::new("..") + let svc = ServeDir::new(SERVE_DIR) .call_fallback_on_method_not_allowed(true) .fallback(tower::service_fn(fallback)); @@ -781,7 +796,7 @@ async fn with_fallback_svc_and_not_append_index_html_on_directories() { )))) } - let svc = ServeDir::new("..") + let svc = ServeDir::new(SERVE_DIR) .append_index_html_on_directories(false) .fallback(tower::service_fn(fallback)); @@ -804,7 +819,7 @@ async fn calls_fallback_on_invalid_paths() { Ok(res) } - let svc = ServeDir::new("..").fallback(service_fn(fallback)); + let svc = ServeDir::new(SERVE_DIR).fallback(service_fn(fallback)); let req = Request::builder() .uri("/weird_%c3%28_path") diff --git a/tower-http/src/services/fs/serve_file.rs b/tower-http/src/services/fs/serve_file.rs index ade3cd15..9422374e 100644 --- a/tower-http/src/services/fs/serve_file.rs +++ b/tower-http/src/services/fs/serve_file.rs @@ -157,51 +157,62 @@ mod tests { use tokio::io::AsyncReadExt; use tower::ServiceExt; + const H_CT: &str = "content-type"; + const H_CT_MD: &str = "text/markdown"; + const H_CT_TXT: &str = "text/plain"; + const SERVE_FILE: &str = "../README.md"; + const SERVE_FILE_START: &str = "# Tower HTTP"; + const SERVE_FILE_PRECOMP: &str = "../test-files/precompressed.txt"; + const SERVE_FILE_PRECOMP_START: &str = "Test file"; + const SERVE_FILE_MISSING_PRECOMP: &str = "../test-files/missing_precompressed.txt"; + const SERVE_FILE_ONLY_GZIP: &str = "../test-files/only_gzipped.txt"; + const SERVE_FILE_PRECOMP_BR: &str = "../test-files/precompressed_br.txt"; + #[tokio::test] async fn basic() { - let svc = ServeFile::new("../README.md"); + let svc = ServeFile::new(SERVE_FILE); let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/markdown"); + assert_eq!(res.headers()[H_CT], H_CT_MD); let body = res.into_body().collect().await.unwrap().to_bytes(); let body = String::from_utf8(body.to_vec()).unwrap(); - assert!(body.starts_with("# Tower HTTP")); + assert!(body.starts_with(SERVE_FILE_START)); } #[tokio::test] async fn basic_with_mime() { - let svc = ServeFile::new_with_mime("../README.md", &Mime::from_str("image/jpg").unwrap()); + let svc = ServeFile::new_with_mime(SERVE_FILE, &Mime::from_str("image/jpg").unwrap()); let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); - assert_eq!(res.headers()["content-type"], "image/jpg"); + assert_eq!(res.headers()[H_CT], "image/jpg"); let body = res.into_body().collect().await.unwrap().to_bytes(); let body = String::from_utf8(body.to_vec()).unwrap(); - assert!(body.starts_with("# Tower HTTP")); + assert!(body.starts_with(SERVE_FILE_START)); } #[tokio::test] async fn head_request() { - let svc = ServeFile::new("../test-files/precompressed.txt"); + let svc = ServeFile::new(SERVE_FILE_PRECOMP); let mut request = Request::new(Body::empty()); *request.method_mut() = Method::HEAD; let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); - assert_eq!(res.headers()["content-length"], "23"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); + assert_eq!(res.headers()["content-length"], "10"); assert!(res.into_body().frame().await.is_none()); } #[tokio::test] async fn precompresed_head_request() { - let svc = ServeFile::new("../test-files/precompressed.txt").precompressed_gzip(); + let svc = ServeFile::new(SERVE_FILE_PRECOMP).precompressed_gzip(); let request = Request::builder() .header("Accept-Encoding", "gzip") @@ -210,16 +221,16 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "gzip"); - assert_eq!(res.headers()["content-length"], "59"); + assert_eq!(res.headers()["content-length"], "39"); assert!(res.into_body().frame().await.is_none()); } #[tokio::test] async fn precompressed_gzip() { - let svc = ServeFile::new("../test-files/precompressed.txt").precompressed_gzip(); + let svc = ServeFile::new(SERVE_FILE_PRECOMP).precompressed_gzip(); let request = Request::builder() .header("Accept-Encoding", "gzip") @@ -227,19 +238,19 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); 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(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn unsupported_precompression_alogrithm_fallbacks_to_uncompressed() { - let svc = ServeFile::new("../test-files/precompressed.txt").precompressed_gzip(); + let svc = ServeFile::new(SERVE_FILE_PRECOMP).precompressed_gzip(); let request = Request::builder() .header("Accept-Encoding", "br") @@ -247,17 +258,17 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); 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(); - assert!(body.starts_with("\"This is a test file!\"")); + assert!(body.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn missing_precompressed_variant_fallbacks_to_uncompressed() { - let svc = ServeFile::new("../test-files/missing_precompressed.txt").precompressed_gzip(); + let svc = ServeFile::new(SERVE_FILE_MISSING_PRECOMP).precompressed_gzip(); let request = Request::builder() .header("Accept-Encoding", "gzip") @@ -265,18 +276,18 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); // Uncompressed file is served because compressed version is missing 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(); - assert!(body.starts_with("Test file!")); + assert!(body.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn missing_precompressed_variant_fallbacks_to_uncompressed_head_request() { - let svc = ServeFile::new("../test-files/missing_precompressed.txt").precompressed_gzip(); + let svc = ServeFile::new(SERVE_FILE_MISSING_PRECOMP).precompressed_gzip(); let request = Request::builder() .header("Accept-Encoding", "gzip") @@ -285,8 +296,8 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); - assert_eq!(res.headers()["content-length"], "11"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); + assert_eq!(res.headers()["content-length"], "10"); // Uncompressed file is served because compressed version is missing assert!(res.headers().get("content-encoding").is_none()); @@ -295,7 +306,7 @@ mod tests { #[tokio::test] async fn only_precompressed_variant_existing() { - let svc = ServeFile::new("../test-files/only_gzipped.txt").precompressed_gzip(); + let svc = ServeFile::new(SERVE_FILE_ONLY_GZIP).precompressed_gzip(); let request = Request::builder().body(Body::empty()).unwrap(); let res = svc.clone().oneshot(request).await.unwrap(); @@ -309,19 +320,19 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); 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(); - assert!(decompressed.starts_with("\"This is a test file\"")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn precompressed_br() { - let svc = ServeFile::new("../test-files/precompressed.txt").precompressed_br(); + let svc = ServeFile::new(SERVE_FILE_PRECOMP).precompressed_br(); let request = Request::builder() .header("Accept-Encoding", "gzip,br") @@ -329,57 +340,57 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "br"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decompressed = Vec::new(); BrotliDecompress(&mut &body[..], &mut decompressed).unwrap(); let decompressed = String::from_utf8(decompressed.to_vec()).unwrap(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn precompressed_deflate() { - let svc = ServeFile::new("../test-files/precompressed.txt").precompressed_deflate(); + let svc = ServeFile::new(SERVE_FILE_PRECOMP).precompressed_deflate(); let request = Request::builder() .header("Accept-Encoding", "deflate,br") .body(Body::empty()) .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "deflate"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decoder = DeflateDecoder::new(&body[..]); let mut decompressed = String::new(); decoder.read_to_string(&mut decompressed).unwrap(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn precompressed_zstd() { - let svc = ServeFile::new("../test-files/precompressed.txt").precompressed_zstd(); + let svc = ServeFile::new(SERVE_FILE_PRECOMP).precompressed_zstd(); let request = Request::builder() .header("Accept-Encoding", "zstd,br") .body(Body::empty()) .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "zstd"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decoder = ZstdDecoder::new(&body[..]); let mut decompressed = String::new(); decoder.read_to_string(&mut decompressed).await.unwrap(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn multi_precompressed() { - let svc = ServeFile::new("../test-files/precompressed.txt") + let svc = ServeFile::new(SERVE_FILE_PRECOMP) .precompressed_gzip() .precompressed_br(); @@ -389,14 +400,14 @@ mod tests { .unwrap(); let res = svc.clone().oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); 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(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); let request = Request::builder() .header("Accept-Encoding", "br") @@ -404,33 +415,33 @@ mod tests { .unwrap(); let res = svc.clone().oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "br"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decompressed = Vec::new(); BrotliDecompress(&mut &body[..], &mut decompressed).unwrap(); let decompressed = String::from_utf8(decompressed.to_vec()).unwrap(); - assert!(decompressed.starts_with("\"This is a test file!\"")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn with_custom_chunk_size() { - let svc = ServeFile::new("../README.md").with_buf_chunk_size(1024 * 32); + let svc = ServeFile::new(SERVE_FILE).with_buf_chunk_size(1024 * 32); let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/markdown"); + assert_eq!(res.headers()[H_CT], H_CT_MD); let body = res.into_body().collect().await.unwrap().to_bytes(); let body = String::from_utf8(body.to_vec()).unwrap(); - assert!(body.starts_with("# Tower HTTP")); + assert!(body.starts_with(SERVE_FILE_START)); } #[tokio::test] async fn fallbacks_to_different_precompressed_variant_if_not_found() { - let svc = ServeFile::new("../test-files/precompressed_br.txt") + let svc = ServeFile::new(SERVE_FILE_PRECOMP_BR) .precompressed_gzip() .precompressed_deflate() .precompressed_br(); @@ -441,19 +452,19 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-encoding"], "br"); let body = res.into_body().collect().await.unwrap().to_bytes(); let mut decompressed = Vec::new(); BrotliDecompress(&mut &body[..], &mut decompressed).unwrap(); let decompressed = String::from_utf8(decompressed.to_vec()).unwrap(); - assert!(decompressed.starts_with("Test file")); + assert!(decompressed.starts_with(SERVE_FILE_PRECOMP_START)); } #[tokio::test] async fn fallbacks_to_different_precompressed_variant_if_not_found_head_request() { - let svc = ServeFile::new("../test-files/precompressed_br.txt") + let svc = ServeFile::new(SERVE_FILE_PRECOMP_BR) .precompressed_gzip() .precompressed_deflate() .precompressed_br(); @@ -465,7 +476,7 @@ mod tests { .unwrap(); let res = svc.oneshot(request).await.unwrap(); - assert_eq!(res.headers()["content-type"], "text/plain"); + assert_eq!(res.headers()[H_CT], H_CT_TXT); assert_eq!(res.headers()["content-length"], "15"); assert_eq!(res.headers()["content-encoding"], "br"); @@ -498,7 +509,7 @@ mod tests { #[tokio::test] async fn last_modified() { - let svc = ServeFile::new("../README.md"); + let svc = ServeFile::new(SERVE_FILE); let req = Request::builder().body(Body::empty()).unwrap(); let res = svc.oneshot(req).await.unwrap(); @@ -512,7 +523,7 @@ mod tests { // -- If-Modified-Since - let svc = ServeFile::new("../README.md"); + let svc = ServeFile::new(SERVE_FILE); let req = Request::builder() .header(header::IF_MODIFIED_SINCE, last_modified) .body(Body::empty()) @@ -522,7 +533,7 @@ mod tests { assert_eq!(res.status(), StatusCode::NOT_MODIFIED); assert!(res.into_body().frame().await.is_none()); - let svc = ServeFile::new("../README.md"); + let svc = ServeFile::new(SERVE_FILE); let req = Request::builder() .header(header::IF_MODIFIED_SINCE, "Fri, 09 Aug 1996 14:21:40 GMT") .body(Body::empty()) @@ -536,7 +547,7 @@ mod tests { // -- If-Unmodified-Since - let svc = ServeFile::new("../README.md"); + let svc = ServeFile::new(SERVE_FILE); let req = Request::builder() .header(header::IF_UNMODIFIED_SINCE, last_modified) .body(Body::empty()) @@ -547,7 +558,7 @@ mod tests { let body = res.into_body().collect().await.unwrap().to_bytes(); assert_eq!(body.as_ref(), readme_bytes); - let svc = ServeFile::new("../README.md"); + let svc = ServeFile::new(SERVE_FILE); let req = Request::builder() .header(header::IF_UNMODIFIED_SINCE, "Fri, 09 Aug 1996 14:21:40 GMT") .body(Body::empty())