Skip to content

Commit

Permalink
Fix query string decoding (#2201)
Browse files Browse the repository at this point in the history
`serde_urlencoded` can't decode into `Vec<(&str, &str)` query string
slices that need percent decoding.
  • Loading branch information
david-perez committed Jan 12, 2023
1 parent bcea15a commit 8d09dad
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 4 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.next.toml
Expand Up @@ -9,4 +9,10 @@
# message = "Fix typos in module documentation for generated crates"
# references = ["smithy-rs#920"]
# meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "client | server | all"}
# author = "rcoh"
# author = "rcoh"

[[smithy-rs]]
message = "Fix severe bug where a router fails to deserialize percent-encoded query strings, reporting no operation match when there could be one. If your Smithy model uses an operation with a request URI spec containing [query string literals](https://smithy.io/2.0/spec/http-bindings.html#query-string-literals), you are affected. This fix was released in `aws-smithy-http-server` v0.53.1."
references = ["smithy-rs#2201"]
meta = { "breaking" = false, "tada" = false, "bug" = true, "target" = "server"}
author = "david-perez"
22 changes: 19 additions & 3 deletions rust-runtime/aws-smithy-http-server/src/routing/request_spec.rs
Expand Up @@ -181,13 +181,18 @@ impl RequestSpec {

match req.uri().query() {
Some(query) => {
// We can't use `HashMap<&str, &str>` because a query string key can appear more
// We can't use `HashMap<Cow<str>, Cow<str>>` because a query string key can appear more
// than once e.g. `/?foo=bar&foo=baz`. We _could_ use a multiset e.g. the `hashbag`
// crate.
let res = serde_urlencoded::from_str::<Vec<(&str, &str)>>(query);
// We must deserialize into `Cow<str>`s because `serde_urlencoded` might need to
// return an owned allocated `String` if it has to percent-decode a slice of the query string.
let res = serde_urlencoded::from_str::<Vec<(Cow<str>, Cow<str>)>>(query);

match res {
Err(_) => Match::No,
Err(error) => {
tracing::debug!(query, %error, "failed to deserialize query string");
Match::No
}
Ok(query_map) => {
for query_segment in self.uri_spec.path_and_query.query_segments.0.iter() {
match query_segment {
Expand Down Expand Up @@ -367,6 +372,17 @@ mod tests {
);
}

#[test]
fn encoded_query_string() {
let request_spec =
RequestSpec::from_parts(Method::DELETE, Vec::new(), vec![QuerySegment::Key("foo".to_owned())]);

assert_eq!(
Match::Yes,
request_spec.matches(&req(&Method::DELETE, "/?foo=hello%20world", None))
);
}

fn ab_spec() -> RequestSpec {
RequestSpec::from_parts(
Method::GET,
Expand Down

0 comments on commit 8d09dad

Please sign in to comment.