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 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( *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)) +} 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", + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^