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 Jan 19, 2024
1 parent 7b07043 commit 6d54ce9
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 1 deletion.
8 changes: 7 additions & 1 deletion src/filters/path.rs
Original file line number Diff line number Diff line change
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,16 @@ 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
13 changes: 13 additions & 0 deletions src/reject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ 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 +295,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 +430,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 @@ -523,6 +531,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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 6d54ce9

Please sign in to comment.