From 168b2f227d1959252dc47518641da7d214a81ed3 Mon Sep 17 00:00:00 2001
From: Aravinth Manivannan <realaravinth@batsense.net>
Date: Tue, 31 Aug 2021 02:20:40 +0530
Subject: [PATCH] compile time validation of path (#2350)

* compile time validation of path

* added trybuild err message

* Update Cargo.toml

* add changelog entry

* test more cases of path validation

* fmt

Co-authored-by: Rob Ede <robjtede@icloud.com>
---
 actix-router/src/resource.rs                  |  5 ++-
 actix-web-codegen/CHANGES.md                  |  3 ++
 actix-web-codegen/Cargo.toml                  |  1 +
 actix-web-codegen/src/route.rs                |  2 +
 actix-web-codegen/tests/trybuild.rs           |  1 +
 .../trybuild/route-malformed-path-fail.rs     | 33 +++++++++++++++
 .../trybuild/route-malformed-path-fail.stderr | 42 +++++++++++++++++++
 7 files changed, 86 insertions(+), 1 deletion(-)
 create mode 100644 actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs
 create mode 100644 actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr

diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs
index fbf29cc7..57ce3680 100644
--- a/actix-router/src/resource.rs
+++ b/actix-router/src/resource.rs
@@ -967,7 +967,10 @@ impl ResourceDef {
                 _ => false,
             })
             .unwrap_or_else(|| {
-                panic!(r#"path "{}" contains malformed dynamic segment"#, pattern)
+                panic!(
+                    r#"pattern "{}" contains malformed dynamic segment"#,
+                    pattern
+                )
             });
 
         let (mut param, mut unprocessed) = pattern.split_at(close_idx + 1);
diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md
index a8a901f7..4fd393b4 100644
--- a/actix-web-codegen/CHANGES.md
+++ b/actix-web-codegen/CHANGES.md
@@ -1,6 +1,9 @@
 # Changes
 
 ## Unreleased - 2021-xx-xx
+* In routing macros, paths are now validated at compile time. [#2350]
+
+[#2350]: https://github.com/actix/actix-web/pull/2350
 
 
 ## 0.5.0-beta.3 - 2021-06-17
diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml
index 4d0fd5e2..66f7acf6 100644
--- a/actix-web-codegen/Cargo.toml
+++ b/actix-web-codegen/Cargo.toml
@@ -17,6 +17,7 @@ proc-macro = true
 quote = "1"
 syn = { version = "1", features = ["full", "parsing"] }
 proc-macro2 = "1"
+actix-router = "0.5.0-beta.1"
 
 [dev-dependencies]
 actix-rt = "2.2"
diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs
index 74704252..c2f851a0 100644
--- a/actix-web-codegen/src/route.rs
+++ b/actix-web-codegen/src/route.rs
@@ -3,6 +3,7 @@ extern crate proc_macro;
 use std::collections::HashSet;
 use std::convert::TryFrom;
 
+use actix_router::ResourceDef;
 use proc_macro::TokenStream;
 use proc_macro2::{Span, TokenStream as TokenStream2};
 use quote::{format_ident, quote, ToTokens, TokenStreamExt};
@@ -101,6 +102,7 @@ impl Args {
             match arg {
                 NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
                     None => {
+                        let _ = ResourceDef::new(lit.value());
                         path = Some(lit);
                     }
                     _ => {
diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs
index 12e848cf..c97211e9 100644
--- a/actix-web-codegen/tests/trybuild.rs
+++ b/actix-web-codegen/tests/trybuild.rs
@@ -10,6 +10,7 @@ fn compile_macros() {
     t.compile_fail("tests/trybuild/route-missing-method-fail.rs");
     t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
     t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
+    t.compile_fail("tests/trybuild/route-malformed-path-fail.rs");
 
     t.pass("tests/trybuild/docstring-ok.rs");
 }
diff --git a/actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs
new file mode 100644
index 00000000..1258a6f2
--- /dev/null
+++ b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.rs
@@ -0,0 +1,33 @@
+use actix_web_codegen::get;
+
+#[get("/{")]
+async fn zero() -> &'static str {
+    "malformed resource def"
+}
+
+#[get("/{foo")]
+async fn one() -> &'static str {
+    "malformed resource def"
+}
+
+#[get("/{}")]
+async fn two() -> &'static str {
+    "malformed resource def"
+}
+
+#[get("/*")]
+async fn three() -> &'static str {
+    "malformed resource def"
+}
+
+#[get("/{tail:\\d+}*")]
+async fn four() -> &'static str {
+    "malformed resource def"
+}
+
+#[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")]
+async fn five() -> &'static str {
+    "malformed resource def"
+}
+
+fn main() {}
diff --git a/actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr
new file mode 100644
index 00000000..93c51010
--- /dev/null
+++ b/actix-web-codegen/tests/trybuild/route-malformed-path-fail.stderr
@@ -0,0 +1,42 @@
+error: custom attribute panicked
+ --> $DIR/route-malformed-path-fail.rs:3:1
+  |
+3 | #[get("/{")]
+  | ^^^^^^^^^^^^
+  |
+  = help: message: pattern "{" contains malformed dynamic segment
+
+error: custom attribute panicked
+ --> $DIR/route-malformed-path-fail.rs:8:1
+  |
+8 | #[get("/{foo")]
+  | ^^^^^^^^^^^^^^^
+  |
+  = help: message: pattern "{foo" contains malformed dynamic segment
+
+error: custom attribute panicked
+  --> $DIR/route-malformed-path-fail.rs:13:1
+   |
+13 | #[get("/{}")]
+   | ^^^^^^^^^^^^^
+   |
+   = help: message: Wrong path pattern: "/{}" regex parse error:
+               ((?s-m)^/(?P<>[^/]+))$
+                            ^
+           error: empty capture group name
+
+error: custom attribute panicked
+  --> $DIR/route-malformed-path-fail.rs:23:1
+   |
+23 | #[get("/{tail:\\d+}*")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: message: custom regex is not supported for tail match
+
+error: custom attribute panicked
+  --> $DIR/route-malformed-path-fail.rs:28:1
+   |
+28 | #[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: message: Only 16 dynamic segments are allowed, provided: 17