rwf2 / Rocket

A web framework for Rust.
https://rocket.rs
Other
24.21k stars 1.56k forks source link

Routes to paths with URL-encoded chars fails to match and always results in 404 #2862

Open ludverse opened 1 week ago

ludverse commented 1 week ago

Rocket Version

0.5.1

Operating System

Debian Testing (upgraded last week)

Rust Toolchain Version

rustc 1.80.1 (3f5fd8dd4 2024-08-06)

What happened?

I'm trying to use URL-encoded chars in my routes and Rocket just responds with 404. Rocket manages to compile with the following test case, but the issue is that, as you can see in the log output:

GET /h%C3%A4cker:
   >> No matching routes for GET /h%C3%A4cker.
   >> No 404 catcher registered. Using Rocket default.

when i make the request to the exact same path as the get macro was defined as, Rocket fails to match my request to my route.

This way I've done it in the test case is a reasonable assumption to how Rocket would handle paths as according to this part of the docs, Origins should be URL-encoded.

Test Case

use rocket::{get, launch, routes};

#[get("/h%C3%A4cker")]
fn serve_hacker() -> &'static str {
    "Hello, world!"
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![serve_hacker])
}

#[cfg(test)]
mod test {
    #[test]
    fn test_hacker_get() {
        use rocket::local::blocking::Client;

        let client = Client::tracked(super::rocket()).expect("valid `Rocket`");

        let response = client.get("/h%C3%A4cker").dispatch();
        assert_eq!(response.into_string().unwrap(), "Hello, world!");
    }
}

Log Output

> ROCKET_LOG_LEVEL=debug cargo test
   Compiling rocket-unicode-issue-test v0.1.0 (/home/ludv/coding/misc/rocket-unicode-issue-test)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.11s
     Running unittests src/main.rs (target/debug/deps/rocket_unicode_issue_test-7fb95b3b05290781)

running 1 test
test test::test_hacker_get ... FAILED

failures:

---- test::test_hacker_get stdout ----
-- configuration trace information --
   >> "address" parameter source: rocket::Config::default()
   >> "port" parameter source: rocket::Config::default()
   >> "workers" parameter source: rocket::Config::default()
   >> "max_blocking" parameter source: rocket::Config::default()
   >> "keep_alive" parameter source: rocket::Config::default()
   >> "ident" parameter source: rocket::Config::default()
   >> "ip_header" parameter source: rocket::Config::default()
   >> "limits" parameter source: rocket::Config::default()
   >> "temp_dir" parameter source: rocket::Config::default()
   >> "log_level" parameter source: `ROCKET_` environment variable(s)
   >> "shutdown" parameter source: rocket::Config::default()
   >> "cli_colors" parameter source: rocket::Config::default()
🔧 Configured for debug.
   >> address: 127.0.0.1
   >> port: 8000
   >> workers: 12
   >> max blocking threads: 512
   >> ident: Rocket
   >> IP header: X-Real-IP
   >> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
   >> temp dir: /tmp
   >> http/2: true
   >> keep-alive: 5s
   >> tls: disabled
   >> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
   >> log level: debug
   >> cli colors: true
📬 Routes:
   >> (serve_hacker) GET /h%C3%A4cker
📡 Fairings:
   >> Shield (liftoff, response, singleton)
🛡️  Shield:
   >> X-Frame-Options: SAMEORIGIN
   >> Permissions-Policy: interest-cohort=()
   >> X-Content-Type-Options: nosniff
🚀 Rocket has launched locally
GET /h%C3%A4cker:
   >> No matching routes for GET /h%C3%A4cker.
   >> No 404 catcher registered. Using Rocket default.
thread 'test::test_hacker_get' panicked at src/main.rs:22:9:
assertion `left == right` failed
  left: "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"color-scheme\" content=\"light dark\">\n    <title>404 Not Found</title>\n</head>\n<body align=\"center\">\n    <div role=\"main\" align=\"center\">\n        <h1>404: Not Found</h1>\n        <p>The requested resource could not be found.</p>\n        <hr />\n    </div>\n    <div role=\"contentinfo\" align=\"center\">\n        <small>Rocket</small>\n    </div>\n</body>\n</html>"
 right: "Hello, world!"
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

failures:
    test::test_hacker_get

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass `--bin rocket-unicode-issue-test`
Program exited erroneously with code 101

Additional Context

No response

System Checks

ludverse commented 1 week ago

Oh ok, so actually I realized that the test case I gave actually does match, to /h%25C3%25A4cker, which is just /h%C3%A4cker URL-encoded another time.

So what happened was when I submitted this, was that I simplified the test case in the issue so much that I actually lost the root of the problem. The real problem disappeared when moved the /häcker/ from the rocket.mount() to the get macro. Here's an example that represents more my original code:

#[get("/")]
fn serve_hacker() -> &'static str {
    "Hello, world!"
}

#[get("/<id>")]
fn serve_id<'a>(id: &'a str) -> &'a str {
    id
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/häcker/", routes![serve_hacker, serve_id])
}

if you run this code, you will get an error:

> cargo run
   Compiling rocket-unicode-issue-test v0.1.0 (/home/ludv/coding/misc/rocket-unicode-issue-test)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.29s
     Running `target/debug/rocket-unicode-issue-test`
Error: invalid route base: /häcker/
   >> unexpected token byte 195 at index 2
   >> in src/main.rs:15:21
thread 'main' panicked at src/main.rs:15:21:
aborting due to route base error
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

So the issue I'm actually having is, Rocket::mount panics if you try to pass it any characters that aren't Unicode. So in actuality, I have a way of solving this problem by using the approach I showed off in the original post where I just put /häcker in the get macro instead of the mount base.

I'm sorry for my confusion here, ignore everything in the original post. What should I do with this issue now, should I close it and open a new one for the real problem or just edit?