Skip to content

Commit

Permalink
url decode path segments with warp::path::param()
Browse files Browse the repository at this point in the history
  • Loading branch information
urkle committed Mar 30, 2023
1 parent 99bd129 commit 53beb0b
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/filters/path.rs
Expand Up @@ -131,6 +131,7 @@ use std::str::FromStr;

use futures_util::future;
use http::uri::PathAndQuery;
use percent_encoding::percent_decode_str;

use self::internal::Opaque;
use crate::filter::{filter_fn, one, Filter, FilterBase, Internal, One, Tuple};
Expand Down Expand Up @@ -266,11 +267,12 @@ pub fn end() -> impl Filter<Extract = (), Error = Rejection> + Copy {
pub fn param<T: FromStr + Send + 'static>(
) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
filter_segment(|seg| {
let seg = percent_decode_str(seg).decode_utf8().map_err(|_| reject::invalid_encoded_url())?;
tracing::trace!("param?: {:?}", seg);
if seg.is_empty() {
return Err(reject::not_found());
}
T::from_str(seg).map(one).map_err(|_| reject::not_found())
T::from_str(seg.as_ref()).map(one).map_err(|_| reject::not_found())
})
}

Expand Down
11 changes: 11 additions & 0 deletions src/reject.rs
Expand Up @@ -102,6 +102,10 @@ pub(crate) fn invalid_header(name: &'static str) -> Rejection {
known(InvalidHeader { name })
}

// 400 Bad Request
#[inline]
pub(crate) fn invalid_encoded_url() -> Rejection { known(InvalidEncodedUrl { _p: () }) }

// 400 Bad Request
#[inline]
pub(crate) fn missing_cookie(name: &'static str) -> Rejection {
Expand Down Expand Up @@ -289,6 +293,7 @@ enum_known! {
MissingConnectionUpgrade(crate::ws::MissingConnectionUpgrade),
MissingExtension(crate::ext::MissingExtension),
BodyConsumedMultipleTimes(crate::body::BodyConsumedMultipleTimes),
InvalidEncodedUrl(InvalidEncodedUrl),
}

impl Rejection {
Expand Down Expand Up @@ -423,6 +428,7 @@ impl Rejections {
| Known::MissingHeader(_)
| Known::MissingCookie(_)
| Known::InvalidQuery(_)
| Known::InvalidEncodedUrl(_)
| Known::BodyReadError(_)
| Known::BodyDeserializeError(_) => StatusCode::BAD_REQUEST,
#[cfg(feature = "websocket")]
Expand Down Expand Up @@ -514,6 +520,11 @@ unit_error! {
pub InvalidQuery: "Invalid query string"
}

unit_error! {
/// Invalid encoded url
pub InvalidEncodedUrl: "Invalid encoded url string"
}

unit_error! {
/// HTTP method not allowed
pub MethodNotAllowed: "HTTP method not allowed"
Expand Down
12 changes: 12 additions & 0 deletions tests/path.rs
Expand Up @@ -42,6 +42,18 @@ async fn param() {
let req = warp::test::request().path("/warp");
assert_eq!(req.filter(&s).await.unwrap(), "warp");

// % encoding
let req = warp::test::request().path("/warp%20speed");
assert_eq!(req.filter(&s).await.unwrap(), "warp speed");

// % encoding as int
let req = warp::test::request().path("/%35%30");
assert_eq!(req.filter(&num).await.unwrap(), 50);

// + space encoding, is not decoded
let req = warp::test::request().path("/warp+speed");
assert_eq!(req.filter(&s).await.unwrap(), "warp+speed");

// u32 doesn't extract a non-int
let req = warp::test::request().path("/warp");
assert!(!req.matches(&num).await);
Expand Down
90 changes: 90 additions & 0 deletions tests/query.rs
Expand Up @@ -137,3 +137,93 @@ async fn raw_query() {
let extracted = req.filter(&as_raw).await.unwrap();
assert_eq!(extracted, "foo=bar&baz=quux".to_owned());
}

#[tokio::test]
async fn url_encoded_raw_query() {
let as_raw = warp::query::raw();

let req = warp::test::request().path("/?foo=bar%20hi&baz=quux");

let extracted = req.filter(&as_raw).await.unwrap();
assert_eq!(extracted, "foo=bar%20hi&baz=quux".to_owned());
}

#[tokio::test]
async fn plus_encoded_raw_query() {
let as_raw = warp::query::raw();

let req = warp::test::request().path("/?foo=bar+hi&baz=quux");

let extracted = req.filter(&as_raw).await.unwrap();
assert_eq!(extracted, "foo=bar+hi&baz=quux".to_owned());
}

#[derive(Deserialize, Debug, Eq, PartialEq)]
struct MyArgsWithInt {
string: Option<String>,
number: Option<u32>,
}

#[tokio::test]
async fn query_struct_with_int() {
let as_struct = warp::query::<MyArgsWithInt>();

let req = warp::test::request().path("/?string=text&number=30");

let extracted = req.filter(&as_struct).await.unwrap();
assert_eq!(
extracted,
MyArgsWithInt {
string: Some("text".into()),
number: Some(30)
}
);
}

#[tokio::test]
async fn missing_query_struct_with_int() {
let as_struct = warp::query::<MyArgsWithInt>();

let req = warp::test::request().path("/");

let extracted = req.filter(&as_struct).await.unwrap();
assert_eq!(
extracted,
MyArgsWithInt {
string: None,
number: None
}
);
}

#[tokio::test]
async fn url_encoded_query_struct_with_int() {
let as_struct = warp::query::<MyArgsWithInt>();

let req = warp::test::request().path("/?string=test%20text&number=%33%30");

let extracted = req.filter(&as_struct).await.unwrap();
assert_eq!(
extracted,
MyArgsWithInt {
string: Some("test text".into()),
number: Some(30)
}
);
}

#[tokio::test]
async fn plus_encoded_query_struct() {
let as_struct = warp::query::<MyArgsWithInt>();

let req = warp::test::request().path("/?string=test+text");

let extracted = req.filter(&as_struct).await.unwrap();
assert_eq!(
extracted,
MyArgsWithInt {
string: Some("test text".into()),
number: None
}
);
}

0 comments on commit 53beb0b

Please sign in to comment.