mirror of
https://github.com/actix/actix-web.git
synced 2024-11-29 13:01:09 +00:00
40048a5811
Co-authored-by: Rob Ede <robjtede@icloud.com>
140 lines
3.8 KiB
Rust
140 lines
3.8 KiB
Rust
use crate::ResourcePath;
|
|
|
|
use crate::Quoter;
|
|
|
|
thread_local! {
|
|
static DEFAULT_QUOTER: Quoter = Quoter::new(b"", b"%/+");
|
|
}
|
|
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct Url {
|
|
uri: http::Uri,
|
|
path: Option<String>,
|
|
}
|
|
|
|
impl Url {
|
|
#[inline]
|
|
pub fn new(uri: http::Uri) -> Url {
|
|
let path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path()));
|
|
Url { uri, path }
|
|
}
|
|
|
|
#[inline]
|
|
pub fn new_with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
|
|
Url {
|
|
path: quoter.requote_str_lossy(uri.path()),
|
|
uri,
|
|
}
|
|
}
|
|
|
|
/// Returns URI.
|
|
#[inline]
|
|
pub fn uri(&self) -> &http::Uri {
|
|
&self.uri
|
|
}
|
|
|
|
/// Returns path.
|
|
#[inline]
|
|
pub fn path(&self) -> &str {
|
|
match self.path {
|
|
Some(ref path) => path,
|
|
_ => self.uri.path(),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn update(&mut self, uri: &http::Uri) {
|
|
self.uri = uri.clone();
|
|
self.path = DEFAULT_QUOTER.with(|q| q.requote_str_lossy(uri.path()));
|
|
}
|
|
|
|
#[inline]
|
|
pub fn update_with_quoter(&mut self, uri: &http::Uri, quoter: &Quoter) {
|
|
self.uri = uri.clone();
|
|
self.path = quoter.requote_str_lossy(uri.path());
|
|
}
|
|
}
|
|
|
|
impl ResourcePath for Url {
|
|
#[inline]
|
|
fn path(&self) -> &str {
|
|
self.path()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use http::Uri;
|
|
use std::convert::TryFrom;
|
|
|
|
use super::*;
|
|
use crate::{Path, ResourceDef};
|
|
|
|
const PROTECTED: &[u8] = b"%/+";
|
|
|
|
fn match_url(pattern: &'static str, url: impl AsRef<str>) -> Path<Url> {
|
|
let re = ResourceDef::new(pattern);
|
|
let uri = Uri::try_from(url.as_ref()).unwrap();
|
|
let mut path = Path::new(Url::new(uri));
|
|
assert!(re.capture_match_info(&mut path));
|
|
path
|
|
}
|
|
|
|
fn percent_encode(data: &[u8]) -> String {
|
|
data.iter().map(|c| format!("%{:02X}", c)).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn parse_url() {
|
|
let re = "/user/{id}/test";
|
|
|
|
let path = match_url(re, "/user/2345/test");
|
|
assert_eq!(path.get("id").unwrap(), "2345");
|
|
}
|
|
|
|
#[test]
|
|
fn protected_chars() {
|
|
let re = "/user/{id}/test";
|
|
|
|
let encoded = percent_encode(PROTECTED);
|
|
let path = match_url(re, format!("/user/{}/test", encoded));
|
|
// characters in captured segment remain unencoded
|
|
assert_eq!(path.get("id").unwrap(), &encoded);
|
|
|
|
// "%25" should never be decoded into '%' to guarantee the output is a valid
|
|
// percent-encoded format
|
|
let path = match_url(re, "/user/qwe%25/test");
|
|
assert_eq!(path.get("id").unwrap(), "qwe%25");
|
|
|
|
let path = match_url(re, "/user/qwe%25rty/test");
|
|
assert_eq!(path.get("id").unwrap(), "qwe%25rty");
|
|
}
|
|
|
|
#[test]
|
|
fn non_protected_ascii() {
|
|
let non_protected_ascii = ('\u{0}'..='\u{7F}')
|
|
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
|
|
.collect::<String>();
|
|
let encoded = percent_encode(non_protected_ascii.as_bytes());
|
|
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
|
assert_eq!(path.get("id").unwrap(), &non_protected_ascii);
|
|
}
|
|
|
|
#[test]
|
|
fn valid_utf8_multi_byte() {
|
|
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
|
let encoded = percent_encode(test.as_bytes());
|
|
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
|
assert_eq!(path.get("id").unwrap(), &test);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_utf8() {
|
|
let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice());
|
|
let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap();
|
|
let path = Path::new(Url::new(uri));
|
|
|
|
// We should always get a valid utf8 string
|
|
assert!(String::from_utf8(path.as_str().as_bytes().to_owned()).is_ok());
|
|
}
|
|
}
|