Skip to content

Commit a1bf866

Browse files
authored
router: don't decode %25 to '%' (#357)
1 parent 6f4d222 commit a1bf866

File tree

2 files changed

+51
-13
lines changed

2 files changed

+51
-13
lines changed

actix-router/CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changes
22

33
## Unreleased - 2021-xx-xx
4+
* When matching URL parameters, `%25` is kept in the percent-encoded form - no longer decoded to `%`. [#357]
5+
6+
[#357]: https://github.com/actix/actix-net/pull/357
47

58

69
## 0.2.7 - 2021-02-06

actix-router/src/url.rs

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ fn set_bit(array: &mut [u8], ch: u8) {
3131
}
3232

3333
thread_local! {
34-
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"/+");
34+
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
3535
}
3636

3737
#[derive(Default, Clone, Debug)]
@@ -204,24 +204,59 @@ mod tests {
204204
use super::*;
205205
use crate::{Path, ResourceDef};
206206

207+
const PROTECTED: &[u8] = b"%/+";
208+
209+
fn match_url(pattern: &'static str, url: impl AsRef<str>) -> Path<Url> {
210+
let re = ResourceDef::new(pattern);
211+
let uri = Uri::try_from(url.as_ref()).unwrap();
212+
let mut path = Path::new(Url::new(uri));
213+
assert!(re.match_path(&mut path));
214+
path
215+
}
216+
217+
fn percent_encode(data: &[u8]) -> String {
218+
data.into_iter().map(|c| format!("%{:02X}", c)).collect()
219+
}
220+
207221
#[test]
208222
fn test_parse_url() {
209-
let re = ResourceDef::new("/user/{id}/test");
223+
let re = "/user/{id}/test";
210224

211-
let url = Uri::try_from("/user/2345/test").unwrap();
212-
let mut path = Path::new(Url::new(url));
213-
assert!(re.match_path(&mut path));
225+
let path = match_url(re, "/user/2345/test");
214226
assert_eq!(path.get("id").unwrap(), "2345");
215227

216-
let url = Uri::try_from("/user/qwe%25/test").unwrap();
217-
let mut path = Path::new(Url::new(url));
218-
assert!(re.match_path(&mut path));
219-
assert_eq!(path.get("id").unwrap(), "qwe%");
228+
// "%25" should never be decoded into '%' to gurantee the output is a valid
229+
// percent-encoded format
230+
let path = match_url(re, "/user/qwe%25/test");
231+
assert_eq!(path.get("id").unwrap(), "qwe%25");
220232

221-
let url = Uri::try_from("/user/qwe%25rty/test").unwrap();
222-
let mut path = Path::new(Url::new(url));
223-
assert!(re.match_path(&mut path));
224-
assert_eq!(path.get("id").unwrap(), "qwe%rty");
233+
let path = match_url(re, "/user/qwe%25rty/test");
234+
assert_eq!(path.get("id").unwrap(), "qwe%25rty");
235+
}
236+
237+
#[test]
238+
fn test_protected_chars() {
239+
let encoded = percent_encode(PROTECTED);
240+
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
241+
assert_eq!(path.get("id").unwrap(), &encoded);
242+
}
243+
244+
#[test]
245+
fn test_non_protecteed_ascii() {
246+
let nonprotected_ascii = ('\u{0}'..='\u{7F}')
247+
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
248+
.collect::<String>();
249+
let encoded = percent_encode(nonprotected_ascii.as_bytes());
250+
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
251+
assert_eq!(path.get("id").unwrap(), &nonprotected_ascii);
252+
}
253+
254+
#[test]
255+
fn test_valid_utf8_multibyte() {
256+
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
257+
let encoded = percent_encode(test.as_bytes());
258+
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
259+
assert_eq!(path.get("id").unwrap(), &test);
225260
}
226261

227262
#[test]

0 commit comments

Comments
 (0)