mirror of
https://github.com/fafhrd91/actix-net
synced 2025-01-18 23:21:50 +01:00
add params decoding
This commit is contained in:
parent
ac0e8b9e53
commit
877614a494
@ -159,6 +159,25 @@ impl<T: ResourcePath> Path<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http")]
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[cfg(feature = "http")]
|
||||||
|
impl Path<crate::Url> {
|
||||||
|
/// Get URL-decoded matched parameter by name without type conversion
|
||||||
|
pub fn get_decoded(&self, key: &str) -> Option<Cow<str>> {
|
||||||
|
use crate::url::RESERVED_QUOTER;
|
||||||
|
|
||||||
|
self.get(key).map(|value| {
|
||||||
|
if let Some(value) = RESERVED_QUOTER.with(|q| q.requote(value.as_bytes())) {
|
||||||
|
Cow::Owned(value)
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PathIter<'a, T> {
|
pub struct PathIter<'a, T> {
|
||||||
idx: usize,
|
idx: usize,
|
||||||
@ -208,3 +227,32 @@ impl<T: ResourcePath> Resource<T> for Path<T> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "http")]
|
||||||
|
#[test]
|
||||||
|
fn test_get_param_by_name() {
|
||||||
|
use crate::Url;
|
||||||
|
use http::{HttpTryFrom, Uri};
|
||||||
|
|
||||||
|
let mut params = Path::new(Url::new(Uri::try_from("/").unwrap()));
|
||||||
|
params.add_static("item1", "path");
|
||||||
|
params.add_static("item2", "http%3A%2F%2Flocalhost%3A80%2Ffoo");
|
||||||
|
|
||||||
|
assert_eq!(params.get("item0"), None);
|
||||||
|
assert_eq!(params.get_decoded("item0"), None);
|
||||||
|
assert_eq!(params.get("item1"), Some("path"));
|
||||||
|
assert_eq!(params.get_decoded("item1").unwrap().to_owned(), "path");
|
||||||
|
assert_eq!(
|
||||||
|
params.get("item2"),
|
||||||
|
Some("http%3A%2F%2Flocalhost%3A80%2Ffoo")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
params.get_decoded("item2").unwrap().to_owned(),
|
||||||
|
"http://localhost:80/foo"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,26 +1,13 @@
|
|||||||
|
use http::Uri;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::ResourcePath;
|
use crate::ResourcePath;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
// https://tools.ietf.org/html/rfc3986#section-2.2
|
||||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|";
|
||||||
#[allow(dead_code)]
|
|
||||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
// https://tools.ietf.org/html/rfc3986#section-2.3
|
||||||
#[allow(dead_code)]
|
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~";
|
||||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~";
|
|
||||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
|
||||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
||||||
1234567890
|
|
||||||
-._~
|
|
||||||
!$'()*,";
|
|
||||||
const QS: &[u8] = b"+&=;b";
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
fn bit_at(array: &[u8], ch: u8) -> bool {
|
||||||
@ -33,23 +20,26 @@ fn set_bit(array: &mut [u8], ch: u8) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
|
static UNRESERVED_QUOTER: Quoter = { Quoter::new(UNRESERVED) };
|
||||||
|
pub(crate) static RESERVED_QUOTER: Quoter = { Quoter::new(RESERVED_PLUS_EXTRA) };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct Url {
|
pub struct Url {
|
||||||
uri: http::Uri,
|
uri: Uri,
|
||||||
path: Option<Rc<String>>,
|
path: Option<Rc<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Url {
|
impl Url {
|
||||||
pub fn new(uri: http::Uri) -> Url {
|
pub fn new(uri: Uri) -> Url {
|
||||||
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
let path = UNRESERVED_QUOTER
|
||||||
|
.with(|q| q.requote(uri.path().as_bytes()))
|
||||||
|
.map(Rc::new);
|
||||||
|
|
||||||
Url { uri, path }
|
Url { uri, path }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uri(&self) -> &http::Uri {
|
pub fn uri(&self) -> &Uri {
|
||||||
&self.uri
|
&self.uri
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,7 +53,9 @@ impl Url {
|
|||||||
|
|
||||||
pub fn update(&mut self, uri: &http::Uri) {
|
pub fn update(&mut self, uri: &http::Uri) {
|
||||||
self.uri = uri.clone();
|
self.uri = uri.clone();
|
||||||
self.path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
self.path = UNRESERVED_QUOTER
|
||||||
|
.with(|q| q.requote(uri.path().as_bytes()))
|
||||||
|
.map(Rc::new);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,40 +67,23 @@ impl ResourcePath for Url {
|
|||||||
|
|
||||||
pub(crate) struct Quoter {
|
pub(crate) struct Quoter {
|
||||||
safe_table: [u8; 16],
|
safe_table: [u8; 16],
|
||||||
protected_table: [u8; 16],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Quoter {
|
impl Quoter {
|
||||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
pub fn new(safe: &[u8]) -> Quoter {
|
||||||
let mut q = Quoter {
|
let mut q = Quoter {
|
||||||
safe_table: [0; 16],
|
safe_table: [0; 16],
|
||||||
protected_table: [0; 16],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// prepare safe table
|
// prepare safe table
|
||||||
for i in 0..128 {
|
|
||||||
if ALLOWED.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
if QS.contains(&i) {
|
|
||||||
set_bit(&mut q.safe_table, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for ch in safe {
|
for ch in safe {
|
||||||
set_bit(&mut q.safe_table, *ch)
|
set_bit(&mut q.safe_table, *ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare protected table
|
|
||||||
for ch in protected {
|
|
||||||
set_bit(&mut q.safe_table, *ch);
|
|
||||||
set_bit(&mut q.protected_table, *ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
q
|
q
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn requote(&self, val: &[u8]) -> Option<Rc<String>> {
|
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
||||||
let mut has_pct = 0;
|
let mut has_pct = 0;
|
||||||
let mut pct = [b'%', 0, 0];
|
let mut pct = [b'%', 0, 0];
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
@ -127,19 +102,17 @@ impl Quoter {
|
|||||||
|
|
||||||
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
||||||
if ch < 128 {
|
if ch < 128 {
|
||||||
if bit_at(&self.protected_table, ch) {
|
|
||||||
buf.extend_from_slice(&pct);
|
|
||||||
idx += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if bit_at(&self.safe_table, ch) {
|
if bit_at(&self.safe_table, ch) {
|
||||||
buf.push(ch);
|
buf.push(ch);
|
||||||
idx += 1;
|
idx += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.extend_from_slice(&pct);
|
||||||
|
} else {
|
||||||
|
// Not ASCII, decode it
|
||||||
|
buf.push(ch);
|
||||||
}
|
}
|
||||||
buf.push(ch);
|
|
||||||
} else {
|
} else {
|
||||||
buf.extend_from_slice(&pct[..]);
|
buf.extend_from_slice(&pct[..]);
|
||||||
}
|
}
|
||||||
@ -160,7 +133,7 @@ impl Quoter {
|
|||||||
if let Some(data) = cloned {
|
if let Some(data) = cloned {
|
||||||
// Unsafe: we get data from http::Uri, which does utf-8 checks already
|
// Unsafe: we get data from http::Uri, which does utf-8 checks already
|
||||||
// this code only decodes valid pct encoded values
|
// this code only decodes valid pct encoded values
|
||||||
Some(Rc::new(unsafe { String::from_utf8_unchecked(data) }))
|
Some(unsafe { String::from_utf8_unchecked(data) })
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -187,28 +160,36 @@ fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use http::{HttpTryFrom, Uri};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{Path, ResourceDef};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_url() {
|
fn decode_path() {
|
||||||
let re = ResourceDef::new("/user/{id}/test");
|
assert_eq!(
|
||||||
|
UNRESERVED_QUOTER.with(|q| q.requote(b"https://localhost:80/foo")),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
|
||||||
let url = Uri::try_from("/user/2345/test").unwrap();
|
assert_eq!(
|
||||||
let mut path = Path::new(Url::new(url));
|
UNRESERVED_QUOTER
|
||||||
assert!(re.match_path(&mut path));
|
.with(|q| q.requote(b"https://localhost:80/foo%25"))
|
||||||
assert_eq!(path.get("id").unwrap(), "2345");
|
.unwrap(),
|
||||||
|
"https://localhost:80/foo%25"
|
||||||
|
);
|
||||||
|
|
||||||
let url = Uri::try_from("/user/qwe%25/test").unwrap();
|
assert_eq!(
|
||||||
let mut path = Path::new(Url::new(url));
|
UNRESERVED_QUOTER
|
||||||
assert!(re.match_path(&mut path));
|
.with(|q| q.requote(b"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo"))
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%");
|
.unwrap(),
|
||||||
|
"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo".to_string()
|
||||||
|
);
|
||||||
|
|
||||||
let url = Uri::try_from("/user/qwe%25rty/test").unwrap();
|
assert_eq!(
|
||||||
let mut path = Path::new(Url::new(url));
|
UNRESERVED_QUOTER
|
||||||
assert!(re.match_path(&mut path));
|
.with(|q| q.requote(
|
||||||
assert_eq!(path.get("id").unwrap(), "qwe%rty");
|
b"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A"
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A".to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user