Repository: ruma/ruma
Pull request #105 (2020-07-02 11:48:21)

Add guard in ruma_api! macro for non-ASCII in path
Returns a compiler error if any non-ASCII characters are found. Add trybuild test for invalid path characters.

farodin91: @jimmycuadra Review?
farodin91: I need to add an event `m.tag`.
farodin91: This could done by different PR. It need to fix ruma/ruma-events#4, before this issue could resolve correct.
farodin91: Waiting for #114 
farodin91: @jimmycuadra Could you start reviewing? Thanks
jimmycuadra: Fantastic work. :D
jplatte: Thanks! Will merge once CI is happy.

Patch:
diff --git a/ruma-api-macros/src/api.rs b/ruma-api-macros/src/api.rs
index fe12fedf..fd43cb6f 100644
--- a/ruma-api-macros/src/api.rs
+++ b/ruma-api-macros/src/api.rs
@@ -269,8 +269,8 @@ impl ToTokens for Api {
                     *http_request.uri_mut() = ruma_api::exports::http::uri::Builder::new()
                         .path_and_query(path_and_query.as_str())
                         .build()
-                        // The only way this can fail is if the path given in the API definition is
-                        // invalid. It is okay to panic in that case.
+                        // The ruma_api! macro guards against invalid path input, but if there are
+                        // invalid (non ASCII) bytes in the fields with the query attribute this will panic.
                         .unwrap();
 
                     { #add_headers_to_request }
diff --git a/ruma-api-macros/src/api/metadata.rs b/ruma-api-macros/src/api/metadata.rs
index c52c17f1..053e8226 100644
--- a/ruma-api-macros/src/api/metadata.rs
+++ b/ruma-api-macros/src/api/metadata.rs
@@ -4,7 +4,7 @@ use std::convert::TryFrom;
 
 use syn::{Expr, ExprLit, ExprPath, Ident, Lit, LitBool, LitStr, Member};
 
-use crate::api::RawMetadata;
+use crate::{api::RawMetadata, util};
 
 /// The result of processing the `metadata` section of the macro.
 pub struct Metadata {
@@ -61,6 +61,13 @@ impl TryFrom<RawMetadata> for Metadata {
                 },
                 "path" => match expr {
                     Expr::Lit(ExprLit { lit: Lit::Str(literal), .. }) => {
+                        let path_str = literal.value();
+                        if !util::is_valid_endpoint_path(&path_str) {
+                            return Err(syn::Error::new_spanned(
+                                literal,
+                                "path may only contain printable ASCII characters with no spaces",
+                            ));
+                        }
                         path = Some(literal);
                     }
                     _ => return Err(syn::Error::new_spanned(expr, "expected a string literal")),
diff --git a/ruma-api-macros/src/util.rs b/ruma-api-macros/src/util.rs
index 256bb93a..dd067cb3 100644
--- a/ruma-api-macros/src/util.rs
+++ b/ruma-api-macros/src/util.rs
@@ -257,3 +257,7 @@ pub(crate) fn req_res_name_value<T>(
     *header = Some(value);
     Ok(field_kind)
 }
+
+pub(crate) fn is_valid_endpoint_path(string: &str) -> bool {
+    string.as_bytes().iter().all(|b| (0x21..=0x7E).contains(b))
+}


Test patch:
diff --git a/ruma-api/tests/ruma_api.rs b/ruma-api/tests/ruma_api.rs
index f56f5a1e..509ea501 100644
--- a/ruma-api/tests/ruma_api.rs
+++ b/ruma-api/tests/ruma_api.rs
@@ -2,4 +2,5 @@
 fn ui() {
     let t = trybuild::TestCases::new();
     t.pass("tests/ui/01-api-sanity-check.rs");
+    t.compile_fail("tests/ui/02-invalid-path.rs");
 }
diff --git a/ruma-api/tests/ui/02-invalid-path.rs b/ruma-api/tests/ui/02-invalid-path.rs
new file mode 100644
index 00000000..0a19ee25
--- /dev/null
+++ b/ruma-api/tests/ui/02-invalid-path.rs
@@ -0,0 +1,39 @@
+use ruma_api::ruma_api;
+
+ruma_api! {
+    metadata: {
+        description: "This will fail.",
+        method: GET,
+        name: "invalid_path",
+        path: "µ/°/§/€",
+        rate_limited: false,
+        requires_authentication: false,
+    }
+
+    request: {
+        #[ruma_api(query_map)]
+        pub fields: Vec<(String, String)>,
+    }
+
+    response: { }
+}
+
+ruma_api! {
+    metadata: {
+        description: "This will fail.",
+        method: GET,
+        name: "invalid_path",
+        path: "path/to/invalid space/endpoint",
+        rate_limited: false,
+        requires_authentication: false,
+    }
+
+    request: {
+        #[ruma_api(query_map)]
+        pub fields: Vec<(String, String)>,
+    }
+
+    response: { }
+}
+
+fn main() {}
diff --git a/ruma-api/tests/ui/02-invalid-path.stderr b/ruma-api/tests/ui/02-invalid-path.stderr
new file mode 100644
index 00000000..44f623a3
--- /dev/null
+++ b/ruma-api/tests/ui/02-invalid-path.stderr
@@ -0,0 +1,11 @@
+error: path may only contain printable ASCII characters with no spaces
+ --> $DIR/02-invalid-path.rs:8:15
+  |
+8 |         path: "µ/°/§/€",
+  |               ^^^^^^^^^
+
+error: path may only contain printable ASCII characters with no spaces
+  --> $DIR/02-invalid-path.rs:26:15
+   |
+26 |         path: "path/to/invalid space/endpoint",
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^