From 42f52e899c747819fb97b7b9878d7b2848f0f04f Mon Sep 17 00:00:00 2001 From: Jose Quintana <1700322+joseluisq@users.noreply.github.com> Date: Mon, 22 Jan 2024 00:34:02 +0100 Subject: [PATCH] fix: wrong glob brace expansion capture in url rewrites & redirects (#304) * fix: wrong glob brace expansion capture in url redirects/rewrites now an url redirect (or rewrite) `source` that uses glob groups with brace expansions like `**/{*}.{jpg,jpeg}` will works as expected: ```toml [advanced] [[advanced.redirects]] source = "**/{*}.{jpg,jpeg}" destination = "http://localhost/new-images/$2.$3" kind = 302 ``` * chore: url rewrites/redirects test cases --- src/settings/mod.rs | 4 ++++ tests/fixtures/toml/redirects.toml | 43 ++++++++++++++++++++++++++++++++++++++++--- tests/fixtures/toml/rewrites.toml | 43 +++++++++++++++++++++++++++++++++++++++++++ tests/redirects.rs | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------- tests/rewrites.rs | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 429 insertions(+), 20 deletions(-) create mode 100644 tests/fixtures/toml/rewrites.toml create mode 100644 tests/rewrites.rs diff --git a/src/settings/mod.rs b/src/settings/mod.rs index f1379cb..60e4a64 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -379,6 +379,8 @@ impl Settings { .regex() .trim_start_matches("(?-u)") .replace("?:.*", ".*") + .replace("?:", "") + .replace(".*.*", ".*") .to_owned(); tracing::debug!( "url rewrites glob pattern: {}", @@ -425,6 +427,8 @@ impl Settings { .regex() .trim_start_matches("(?-u)") .replace("?:.*", ".*") + .replace("?:", "") + .replace(".*.*", ".*") .to_owned(); tracing::debug!( "url redirects glob pattern: {}", diff --git a/tests/fixtures/toml/redirects.toml b/tests/fixtures/toml/redirects.toml index d6a6563..3a2af88 100644 --- a/tests/fixtures/toml/redirects.toml +++ b/tests/fixtures/toml/redirects.toml @@ -4,14 +4,51 @@ root = "docker/public" [advanced] -### URL Redirects +# Host tests [[advanced.redirects]] host = "127.0.0.1:1234" source = "/{*}" destination = "http://localhost:1234/$1" kind = 301 +# Glob groups 1 [[advanced.redirects]] -source = "**/{*}.{*}" -destination = "http://localhost:1234/files/$1.$2" +source = "**/main.{css}" +destination = "http://localhost/new-styles/style.$2" +kind = 301 + +# Glob groups 2 +[[advanced.redirects]] +source = "/{main,style}.css" +destination = "http://localhost/new-styles/$1.min.css" +kind = 301 + +# Glob groups 3 +[[advanced.redirects]] +source = "/{rust,go,c}-lang.{rs,go,c}" +destination = "http://localhost/new-languages/$1.lang.$2" +kind = 302 + +# Glob groups 4 +[[advanced.redirects]] +source = "/assets/{*}.{js,mjs}" +destination = "http://localhost/new-scripts/$1.$2" kind = 302 + +# Glob groups 5 +[[advanced.redirects]] +source = "**/{*}.{jpg,jpeg}" +destination = "http://localhost/new-images/$2.$3" +kind = 302 + +# Glob groups 6 +[[advanced.redirects]] +source = "**/{*}.{ttf,otf,woff}" +destination = "http://localhost/new-fonts/$2.woff" +kind = 302 + +# Glob groups generic 1 +[[advanced.redirects]] +source = "**/{*}.{*}" +destination = "http://localhost/new-generic/$2.$3" +kind = 301 diff --git a/tests/fixtures/toml/rewrites.toml b/tests/fixtures/toml/rewrites.toml new file mode 100644 index 0000000..4a16abf --- /dev/null +++ b/tests/fixtures/toml/rewrites.toml @@ -0,0 +1,43 @@ +[general] + +root = "docker/public" + +[advanced] + +# Glob groups 1 +[[advanced.rewrites]] +source = "**/error-page.{html}" +destination = "/404.$2" + +# Glob groups 2 +[[advanced.rewrites]] +source = "/error-page/{404,50x}.html" +destination = "/$1.html" + +# Glob groups 3 +[[advanced.rewrites]] +source = "/errors/{50}x.html" +destination = "/$1x.html" + +# Glob groups 4 +[[advanced.rewrites]] +source = "/scripts/{*}.{js,mjs}" +destination = "/assets/$1.$2" + +# Glob groups 5 (redirect) +[[advanced.rewrites]] +source = "**/{*}.{ico}" +destination = "/assets/favicon.$3" +redirect = 302 + +# Glob groups 6 (redirect) +[[advanced.rewrites]] +source = "**/{*}.{ttf,otf,woff}" +destination = "http://localhost/new-fonts/$2.woff" +redirect = 302 + +# Glob groups generic 1 (redirect) +[[advanced.rewrites]] +source = "**/{*}.{*}" +destination = "http://localhost/new-generic/$2.$3" +redirect = 301 diff --git a/tests/redirects.rs b/tests/redirects.rs index 4329e2e..aae5f59 100644 --- a/tests/redirects.rs +++ b/tests/redirects.rs @@ -10,23 +10,20 @@ pub mod tests { use static_web_server::testing::fixtures::{fixture_req_handler, REMOTE_ADDR}; #[tokio::test] - async fn redirects_default() { + async fn redirects_skipped() { let req_handler = fixture_req_handler("toml/redirects.toml"); let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); let mut req = Request::default(); - *req.uri_mut() = "http://localhost:1234/assets/favicon.ico".parse().unwrap(); + *req.uri_mut() = "http://localhost".parse().unwrap(); match req_handler.handle(&mut req, remote_addr).await { Ok(res) => { - assert_eq!(res.status(), 302); - assert_eq!( - res.headers()["location"], - "http://localhost:1234/files/assets/favicon.ico" - ); + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-type"], "text/html"); } - Err(status) => { - panic!("expected a status 302 but got {status}") + Err(err) => { + panic!("unexpected error: {err}") } }; } @@ -44,27 +41,162 @@ pub mod tests { assert_eq!(res.status(), 301); assert_eq!(res.headers()["location"], "http://localhost:1234/"); } - Err(status) => { - panic!("expected a status 301 but got {status}") + Err(err) => { + panic!("unexpected error: {err}") } }; } #[tokio::test] - async fn redirects_skipped() { + async fn redirects_glob_groups_1() { let req_handler = fixture_req_handler("toml/redirects.toml"); let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); let mut req = Request::default(); - *req.uri_mut() = "http://localhost:1234".parse().unwrap(); + *req.uri_mut() = "http://localhost/assets/main.css".parse().unwrap(); match req_handler.handle(&mut req, remote_addr).await { Ok(res) => { - assert_eq!(res.status(), 200); - assert_eq!(res.headers()["content-type"], "text/html"); + assert_eq!(res.status(), 301); + assert_eq!( + res.headers()["location"], + "http://localhost/new-styles/style.css" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn redirects_glob_groups_2() { + let req_handler = fixture_req_handler("toml/redirects.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/style.css".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 301); + assert_eq!( + res.headers()["location"], + "http://localhost/new-styles/style.min.css" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn redirects_glob_groups_3() { + let req_handler = fixture_req_handler("toml/redirects.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/rust-lang.rs".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 302); + assert_eq!( + res.headers()["location"], + "http://localhost/new-languages/rust.lang.rs" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn redirects_glob_groups_4() { + let req_handler = fixture_req_handler("toml/redirects.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/assets/main.js".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 302); + assert_eq!( + res.headers()["location"], + "http://localhost/new-scripts/main.js" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn redirects_glob_groups_5() { + let req_handler = fixture_req_handler("toml/redirects.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/images/avatar.jpeg".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 302); + assert_eq!( + res.headers()["location"], + "http://localhost/new-images/images/avatar.jpeg" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn redirects_glob_groups_6() { + let req_handler = fixture_req_handler("toml/redirects.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/fonts/title.ttf".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 302); + assert_eq!( + res.headers()["location"], + "http://localhost/new-fonts/fonts/title.woff" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn redirects_glob_groups_generic_1() { + let req_handler = fixture_req_handler("toml/redirects.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/generic-page.html".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 301); + assert_eq!( + res.headers()["location"], + "http://localhost/new-generic/generic-page.html" + ); } - Err(status) => { - panic!("expected a status 200 but got {status}") + Err(err) => { + panic!("unexpected error: {err}") } }; } diff --git a/tests/rewrites.rs b/tests/rewrites.rs new file mode 100644 index 0000000..fe04ffb --- /dev/null +++ b/tests/rewrites.rs @@ -0,0 +1,193 @@ +#![forbid(unsafe_code)] +#![deny(warnings)] +#![deny(rust_2018_idioms)] +#![deny(dead_code)] + +pub mod tests { + use hyper::Request; + use std::net::SocketAddr; + + use static_web_server::testing::fixtures::{fixture_req_handler, REMOTE_ADDR}; + + #[tokio::test] + async fn rewrites_skipped() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://development".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-type"], "text/html"); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn rewrites_glob_groups_1() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/some/error-page.html".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(mut res) => { + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-type"], "text/html"); + + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` conversion"); + let body_str = std::str::from_utf8(&body).unwrap(); + assert!(body_str.contains("404 Content")) + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn rewrites_glob_groups_2() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/error-page/50x.html".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(mut res) => { + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-type"], "text/html"); + + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` conversion"); + let body_str = std::str::from_utf8(&body).unwrap(); + assert!(body_str.contains("50x Service Unavailable")) + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn rewrites_glob_groups_3() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/errors/50x.html".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(mut res) => { + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-type"], "text/html"); + + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` conversion"); + let body_str = std::str::from_utf8(&body).unwrap(); + assert!(body_str.contains("50x Service Unavailable")) + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn rewrites_glob_groups_4() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/scripts/main.js".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(mut res) => { + assert_eq!(res.status(), 200); + assert_eq!(res.headers()["content-type"], "application/javascript"); + + let body = hyper::body::to_bytes(res.body_mut()) + .await + .expect("unexpected bytes error during `body` conversion"); + let body_str = std::str::from_utf8(&body).unwrap(); + assert!(body_str.contains("Static Web Server")) + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn rewrites_glob_groups_5() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/images/icon.ico".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 302); + assert_eq!(res.headers()["location"], "/assets/favicon.ico"); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn rewrites_glob_groups_6() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/fonts/text.ttf".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 302); + assert_eq!( + res.headers()["location"], + "http://localhost/new-fonts/fonts/text.woff" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } + + #[tokio::test] + async fn rewrites_glob_groups_generic_1() { + let req_handler = fixture_req_handler("toml/rewrites.toml"); + let remote_addr = Some(REMOTE_ADDR.parse::().unwrap()); + + let mut req = Request::default(); + *req.uri_mut() = "http://localhost/generic-page.html".parse().unwrap(); + + match req_handler.handle(&mut req, remote_addr).await { + Ok(res) => { + assert_eq!(res.status(), 301); + assert_eq!( + res.headers()["location"], + "http://localhost/new-generic/generic-page.html" + ); + } + Err(err) => { + panic!("unexpected error: {err}") + } + }; + } +} -- libgit2 1.7.2