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
Diff
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(-)
@@ -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: {}",
@@ -4,14 +4,51 @@ root = "docker/public"
[advanced]
[[advanced.redirects]]
host = "127.0.0.1:1234"
source = "/{*}"
destination = "http://localhost:1234/$1"
kind = 301
[[advanced.redirects]]
source = "**/{*}.{*}"
destination = "http://localhost:1234/files/$1.$2"
source = "**/main.{css}"
destination = "http://localhost/new-styles/style.$2"
kind = 301
[[advanced.redirects]]
source = "/{main,style}.css"
destination = "http://localhost/new-styles/$1.min.css"
kind = 301
[[advanced.redirects]]
source = "/{rust,go,c}-lang.{rs,go,c}"
destination = "http://localhost/new-languages/$1.lang.$2"
kind = 302
[[advanced.redirects]]
source = "/assets/{*}.{js,mjs}"
destination = "http://localhost/new-scripts/$1.$2"
kind = 302
[[advanced.redirects]]
source = "**/{*}.{jpg,jpeg}"
destination = "http://localhost/new-images/$2.$3"
kind = 302
[[advanced.redirects]]
source = "**/{*}.{ttf,otf,woff}"
destination = "http://localhost/new-fonts/$2.woff"
kind = 302
[[advanced.redirects]]
source = "**/{*}.{*}"
destination = "http://localhost/new-generic/$2.$3"
kind = 301
@@ -0,0 +1,43 @@
[general]
root = "docker/public"
[advanced]
[[advanced.rewrites]]
source = "**/error-page.{html}"
destination = "/404.$2"
[[advanced.rewrites]]
source = "/error-page/{404,50x}.html"
destination = "/$1.html"
[[advanced.rewrites]]
source = "/errors/{50}x.html"
destination = "/$1x.html"
[[advanced.rewrites]]
source = "/scripts/{*}.{js,mjs}"
destination = "/assets/$1.$2"
[[advanced.rewrites]]
source = "**/{*}.{ico}"
destination = "/assets/favicon.$3"
redirect = 302
[[advanced.rewrites]]
source = "**/{*}.{ttf,otf,woff}"
destination = "http://localhost/new-fonts/$2.woff"
redirect = 302
[[advanced.rewrites]]
source = "**/{*}.{*}"
destination = "http://localhost/new-generic/$2.$3"
redirect = 301
@@ -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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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}")
}
};
}
@@ -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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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::<SocketAddr>().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}")
}
};
}
}