index : static-web-server.git

ascending towards madness

author Jose Quintana <1700322+joseluisq@users.noreply.github.com> 2024-01-21 23:34:02.0 +00:00:00
committer GitHub <noreply@github.com> 2024-01-21 23:34:02.0 +00:00:00
commit
42f52e899c747819fb97b7b9878d7b2848f0f04f [patch]
tree
c62540a07e6ed162e951ba4cdb8f4f1d958f57eb
parent
9f2a4f0ba68f2e264496366ebc80532717901b74
download
42f52e899c747819fb97b7b9878d7b2848f0f04f.tar.gz

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(-)

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::<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}")
            }
        };
    }
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::<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}")
            }
        };
    }
}