fully percent decode path segments when capturing

This commit is contained in:
Rob Ede 2022-01-04 13:10:38 +00:00
parent 85c9b1a263
commit d3dfac68d4
No known key found for this signature in database
GPG Key ID: 97C636207D3EF933
3 changed files with 88 additions and 13 deletions

View File

@ -1,8 +1,11 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
- `PathDeserializer` now decodes all percent encoded characters in dynamic segments. [#????]
- Minimum supported Rust version (MSRV) is now 1.54. - Minimum supported Rust version (MSRV) is now 1.54.
[#????]: https://github.com/actix/actix-net/pull/????
## 0.5.0-beta.3 - 2021-12-17 ## 0.5.0-beta.3 - 2021-12-17
- Minimum supported Rust version (MSRV) is now 1.52. - Minimum supported Rust version (MSRV) is now 1.52.

View File

@ -2,7 +2,11 @@ use serde::de::{self, Deserializer, Error as DeError, Visitor};
use serde::forward_to_deserialize_any; use serde::forward_to_deserialize_any;
use crate::path::{Path, PathIter}; use crate::path::{Path, PathIter};
use crate::ResourcePath; use crate::{Quoter, ResourcePath};
thread_local! {
static FULL_QUOTER: Quoter = Quoter::new(b"+/%", b"");
}
macro_rules! unsupported_type { macro_rules! unsupported_type {
($trait_fn:ident, $name:expr) => { ($trait_fn:ident, $name:expr) => {
@ -10,16 +14,13 @@ macro_rules! unsupported_type {
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
Err(de::value::Error::custom(concat!( Err(de::Error::custom(concat!("unsupported type: ", $name)))
"unsupported type: ",
$name
)))
} }
}; };
} }
macro_rules! parse_single_value { macro_rules! parse_single_value {
($trait_fn:ident, $visit_fn:ident, $tp:tt) => { ($trait_fn:ident, $visit_fn:ident, $tp:expr) => {
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error> fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where where
V: Visitor<'de>, V: Visitor<'de>,
@ -33,12 +34,14 @@ macro_rules! parse_single_value {
.as_str(), .as_str(),
)) ))
} else { } else {
let v = self.path[0].parse().map_err(|_| { let decoded = FULL_QUOTER
de::value::Error::custom(format!( .with(|q| q.requote(self.path[0].as_bytes()))
"can not parse {:?} to a {}", .unwrap_or_else(|| self.path[0].to_owned());
&self.path[0], $tp
)) let v = decoded.parse().map_err(|_| {
de::Error::custom(format!("can not parse {:?} to a {}", &self.path[0], $tp))
})?; })?;
visitor.$visit_fn(v) visitor.$visit_fn(v)
} }
} }
@ -185,7 +188,11 @@ impl<'de, T: ResourcePath + 'de> Deserializer<'de> for PathDeserializer<'de, T>
.as_str(), .as_str(),
)) ))
} else { } else {
visitor.visit_str(&self.path[0]) let decoded = FULL_QUOTER
.with(|q| q.requote(self.path[0].as_bytes()))
.unwrap_or_else(|| self.path[0].to_owned());
visitor.visit_str(&decoded)
} }
} }
@ -285,9 +292,14 @@ macro_rules! parse_value {
where where
V: Visitor<'de>, V: Visitor<'de>,
{ {
let v = self.value.parse().map_err(|_| { let decoded = FULL_QUOTER
.with(|q| q.requote(self.value.as_bytes()))
.unwrap_or_else(|| self.value.to_owned());
let v = decoded.parse().map_err(|_| {
de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp)) de::value::Error::custom(format!("can not parse {:?} to a {}", self.value, $tp))
})?; })?;
visitor.$visit_fn(v) visitor.$visit_fn(v)
} }
}; };
@ -497,6 +509,7 @@ mod tests {
use super::*; use super::*;
use crate::path::Path; use crate::path::Path;
use crate::router::Router; use crate::router::Router;
use crate::ResourceDef;
#[derive(Deserialize)] #[derive(Deserialize)]
struct MyStruct { struct MyStruct {
@ -657,6 +670,53 @@ mod tests {
assert!(format!("{:?}", s).contains("can not parse")); assert!(format!("{:?}", s).contains("can not parse"));
} }
#[test]
fn deserialize_path_decode_string() {
let rdef = ResourceDef::new("/{key}");
let mut path = Path::new("/%25");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let segment: String = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment, "%");
let mut path = Path::new("/%2F");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let segment: String = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment, "/")
}
#[test]
fn deserialize_path_decode_seq() {
let rdef = ResourceDef::new("/{key}/{value}");
let mut path = Path::new("/%25/%2F");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let segment: (String, String) = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(segment.0, "%");
assert_eq!(segment.1, "/");
}
#[test]
fn deserialize_path_decode_map() {
#[derive(Deserialize)]
struct Vals {
key: String,
value: String,
}
let rdef = ResourceDef::new("/{key}/{value}");
let mut path = Path::new("/%25/%2F");
rdef.capture_match_info(&mut path);
let de = PathDeserializer::new(&path);
let vals: Vals = serde::Deserialize::deserialize(de).unwrap();
assert_eq!(vals.key, "%");
assert_eq!(vals.value, "/");
}
// #[test] // #[test]
// fn test_extract_path_decode() { // fn test_extract_path_decode() {
// let mut router = Router::<()>::default(); // let mut router = Router::<()>::default();

View File

@ -285,6 +285,18 @@ mod tests {
assert_eq!(res[1], "32".to_owned()); assert_eq!(res[1], "32".to_owned());
} }
#[actix_rt::test]
async fn paths_decoded() {
let resource = ResourceDef::new("/{key}/{value}");
let mut req = TestRequest::with_uri("/na%2Bme/us%2Fer%251").to_srv_request();
resource.capture_match_info(req.match_info_mut());
let (req, mut pl) = req.into_parts();
let path_items = Path::<MyStruct>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(path_items.key, "na+me");
assert_eq!(path_items.value, "us/er%1");
}
#[actix_rt::test] #[actix_rt::test]
async fn test_custom_err_handler() { async fn test_custom_err_handler() {
let (req, mut pl) = TestRequest::with_uri("/name/user1/") let (req, mut pl) = TestRequest::with_uri("/name/user1/")