mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-29 00:07:48 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ac9fc662c6 | ||
|
0745a1a9f8 | ||
|
b1635bc0e6 | ||
|
08c7743bb8 | ||
|
68c5d6e6d6 | ||
|
c386353337 | ||
|
9aab382ea8 | ||
|
389cb13cd6 | ||
|
6a93178479 |
19
CHANGES.md
19
CHANGES.md
@@ -1,5 +1,21 @@
|
||||
# Changes
|
||||
|
||||
## [0.7.15] - 2018-12-05
|
||||
|
||||
## Changed
|
||||
|
||||
* `ClientConnector::resolver` now accepts `Into<Recipient>` instead of `Addr`. It enables user to implement own resolver.
|
||||
|
||||
* `QueryConfig` and `PathConfig` are made public.
|
||||
|
||||
* `AsyncResult::async` is changed to `AsyncResult::future` as `async` is reserved keyword in 2018 edition.
|
||||
|
||||
### Added
|
||||
|
||||
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||
with `PathConfig::default().disable_decoding()`
|
||||
|
||||
|
||||
## [0.7.14] - 2018-11-14
|
||||
|
||||
### Added
|
||||
@@ -8,6 +24,9 @@
|
||||
|
||||
* Add method to configure `SameSite` option in `CookieIdentityPolicy`.
|
||||
|
||||
* By default, `Path` extractor now percent decode all characters. This behaviour can be disabled
|
||||
with `PathConfig::default().disable_decoding()`
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
|
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.7.14"
|
||||
version = "0.7.15"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@@ -61,7 +61,7 @@ flate2-rust = ["flate2/rust_backend"]
|
||||
cell = ["actix-net/cell"]
|
||||
|
||||
[dependencies]
|
||||
actix = "0.7.6"
|
||||
actix = "0.7.7"
|
||||
actix-net = "0.2.2"
|
||||
|
||||
askama_escape = "0.1.0"
|
||||
@@ -76,7 +76,7 @@ mime = "0.3"
|
||||
mime_guess = "2.0.0-alpha"
|
||||
num_cpus = "1.0"
|
||||
percent-encoding = "1.0"
|
||||
rand = "0.5"
|
||||
rand = "0.6"
|
||||
regex = "1.0"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
@@ -87,7 +87,7 @@ encoding = "0.2"
|
||||
language-tags = "0.2"
|
||||
lazy_static = "1.0"
|
||||
lazycell = "1.0.0"
|
||||
parking_lot = "0.6"
|
||||
parking_lot = "0.7"
|
||||
serde_urlencoded = "^0.5.3"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
cookie = { version="0.11", features=["percent-encode"] }
|
||||
@@ -127,7 +127,7 @@ webpki-roots = { version = "0.15", optional = true }
|
||||
tokio-uds = { version="0.2", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.5"
|
||||
env_logger = "0.6"
|
||||
serde_derive = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
|
30
MIGRATION.md
30
MIGRATION.md
@@ -1,3 +1,33 @@
|
||||
## 0.7.15
|
||||
|
||||
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
||||
your routes, you should use `%20`.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let app = App::new().resource("/my index", |r| {
|
||||
r.method(http::Method::GET)
|
||||
.with(index);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
use
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let app = App::new().resource("/my%20index", |r| {
|
||||
r.method(http::Method::GET)
|
||||
.with(index);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
* If you used `AsyncResult::async` you need to replace it with `AsyncResult::future`
|
||||
|
||||
|
||||
## 0.7.4
|
||||
|
||||
* `Route::with_config()`/`Route::with_async_config()` always passes configuration objects as tuple
|
||||
|
39
src/body.rs
39
src/body.rs
@@ -1,5 +1,6 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::Stream;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, mem};
|
||||
|
||||
@@ -194,12 +195,30 @@ impl From<Vec<u8>> for Binary {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, [u8]>> for Binary {
|
||||
fn from(b: Cow<'static, [u8]>) -> Binary {
|
||||
match b {
|
||||
Cow::Borrowed(s) => Binary::Slice(s),
|
||||
Cow::Owned(vec) => Binary::Bytes(Bytes::from(vec)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Binary {
|
||||
fn from(s: String) -> Binary {
|
||||
Binary::Bytes(Bytes::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for Binary {
|
||||
fn from(s: Cow<'static, str>) -> Binary {
|
||||
match s {
|
||||
Cow::Borrowed(s) => Binary::Slice(s.as_ref()),
|
||||
Cow::Owned(s) => Binary::Bytes(Bytes::from(s)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a String> for Binary {
|
||||
fn from(s: &'a String) -> Binary {
|
||||
Binary::Bytes(Bytes::from(AsRef::<[u8]>::as_ref(&s)))
|
||||
@@ -287,6 +306,16 @@ mod tests {
|
||||
assert_eq!(Binary::from("test").as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cow_str() {
|
||||
let cow: Cow<'static, str> = Cow::Borrowed("test");
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
let cow: Cow<'static, str> = Cow::Owned("test".to_owned());
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_bytes() {
|
||||
assert_eq!(Binary::from(b"test".as_ref()).len(), 4);
|
||||
@@ -307,6 +336,16 @@ mod tests {
|
||||
assert_eq!(Binary::from(Bytes::from("test")).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cow_bytes() {
|
||||
let cow: Cow<'static, [u8]> = Cow::Borrowed(b"test");
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
let cow: Cow<'static, [u8]> = Cow::Owned(Vec::from("test"));
|
||||
assert_eq!(Binary::from(cow.clone()).len(), 4);
|
||||
assert_eq!(Binary::from(cow.clone()).as_ref(), b"test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arc_string() {
|
||||
let b = Arc::new("test".to_owned());
|
||||
|
@@ -5,7 +5,7 @@ use std::{fmt, io, mem, time};
|
||||
|
||||
use actix::resolver::{Connect as ResolveConnect, Resolver, ResolverError};
|
||||
use actix::{
|
||||
fut, Actor, ActorFuture, ActorResponse, Addr, AsyncContext, Context,
|
||||
fut, Actor, ActorFuture, ActorResponse, AsyncContext, Context,
|
||||
ContextFutureSpawner, Handler, Message, Recipient, StreamHandler, Supervised,
|
||||
SystemService, WrapFuture,
|
||||
};
|
||||
@@ -220,7 +220,7 @@ pub struct ClientConnector {
|
||||
acq_tx: mpsc::UnboundedSender<AcquiredConnOperation>,
|
||||
acq_rx: Option<mpsc::UnboundedReceiver<AcquiredConnOperation>>,
|
||||
|
||||
resolver: Option<Addr<Resolver>>,
|
||||
resolver: Option<Recipient<ResolveConnect>>,
|
||||
conn_lifetime: Duration,
|
||||
conn_keep_alive: Duration,
|
||||
limit: usize,
|
||||
@@ -239,7 +239,7 @@ impl Actor for ClientConnector {
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
if self.resolver.is_none() {
|
||||
self.resolver = Some(Resolver::from_registry())
|
||||
self.resolver = Some(Resolver::from_registry().recipient())
|
||||
}
|
||||
self.collect_periodic(ctx);
|
||||
ctx.add_stream(self.acq_rx.take().unwrap());
|
||||
@@ -503,8 +503,10 @@ impl ClientConnector {
|
||||
}
|
||||
|
||||
/// Use custom resolver actor
|
||||
pub fn resolver(mut self, addr: Addr<Resolver>) -> Self {
|
||||
self.resolver = Some(addr);
|
||||
///
|
||||
/// By default actix's Resolver is used.
|
||||
pub fn resolver<A: Into<Recipient<ResolveConnect>>>(mut self, addr: A) -> Self {
|
||||
self.resolver = Some(addr.into());
|
||||
self
|
||||
}
|
||||
|
||||
@@ -940,7 +942,7 @@ impl Handler<Connect> for ClientConnector {
|
||||
}
|
||||
|
||||
let host = uri.host().unwrap().to_owned();
|
||||
let port = uri.port().unwrap_or_else(|| proto.port());
|
||||
let port = uri.port_part().map(|port| port.as_u16()).unwrap_or_else(|| proto.port());
|
||||
let key = Key {
|
||||
host,
|
||||
port,
|
||||
|
@@ -631,7 +631,7 @@ impl ClientRequestBuilder {
|
||||
if !parts.headers.contains_key(header::HOST) {
|
||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||
|
||||
let _ = match parts.uri.port() {
|
||||
let _ = match parts.uri.port_part().map(|port| port.as_u16()) {
|
||||
None | Some(80) | Some(443) => write!(wrt, "{}", host),
|
||||
Some(port) => write!(wrt, "{}:{}", host, port),
|
||||
};
|
||||
|
70
src/de.rs
70
src/de.rs
@@ -1,7 +1,10 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::de::{self, Deserializer, Error as DeError, Visitor};
|
||||
|
||||
use httprequest::HttpRequest;
|
||||
use param::ParamsIter;
|
||||
use uri::RESERVED_QUOTER;
|
||||
|
||||
macro_rules! unsupported_type {
|
||||
($trait_fn:ident, $name:expr) => {
|
||||
@@ -13,6 +16,20 @@ macro_rules! unsupported_type {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! percent_decode_if_needed {
|
||||
($value:expr, $decode:expr) => {
|
||||
if $decode {
|
||||
if let Some(ref mut value) = RESERVED_QUOTER.requote($value.as_bytes()) {
|
||||
Rc::make_mut(value).parse()
|
||||
} else {
|
||||
$value.parse()
|
||||
}
|
||||
} else {
|
||||
$value.parse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! parse_single_value {
|
||||
($trait_fn:ident, $visit_fn:ident, $tp:tt) => {
|
||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
@@ -23,11 +40,11 @@ macro_rules! parse_single_value {
|
||||
format!("wrong number of parameters: {} expected 1",
|
||||
self.req.match_info().len()).as_str()))
|
||||
} else {
|
||||
let v = self.req.match_info()[0].parse().map_err(
|
||||
|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}",
|
||||
&self.req.match_info()[0], $tp)))?;
|
||||
visitor.$visit_fn(v)
|
||||
let v_parsed = percent_decode_if_needed!(&self.req.match_info()[0], self.decode)
|
||||
.map_err(|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", &self.req.match_info()[0], $tp)
|
||||
))?;
|
||||
visitor.$visit_fn(v_parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,11 +52,12 @@ macro_rules! parse_single_value {
|
||||
|
||||
pub struct PathDeserializer<'de, S: 'de> {
|
||||
req: &'de HttpRequest<S>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de, S: 'de> PathDeserializer<'de, S> {
|
||||
pub fn new(req: &'de HttpRequest<S>) -> Self {
|
||||
PathDeserializer { req }
|
||||
pub fn new(req: &'de HttpRequest<S>, decode: bool) -> Self {
|
||||
PathDeserializer { req, decode }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +71,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
visitor.visit_map(ParamsDeserializer {
|
||||
params: self.req.match_info().iter(),
|
||||
current: None,
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -107,6 +126,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
} else {
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.req.match_info().iter(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -128,6 +148,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
} else {
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.req.match_info().iter(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -141,28 +162,13 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
Err(de::value::Error::custom("unsupported type: enum"))
|
||||
}
|
||||
|
||||
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
if self.req.match_info().len() != 1 {
|
||||
Err(de::value::Error::custom(
|
||||
format!(
|
||||
"wrong number of parameters: {} expected 1",
|
||||
self.req.match_info().len()
|
||||
).as_str(),
|
||||
))
|
||||
} else {
|
||||
visitor.visit_str(&self.req.match_info()[0])
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where
|
||||
V: Visitor<'de>,
|
||||
{
|
||||
visitor.visit_seq(ParamsSeq {
|
||||
params: self.req.match_info().iter(),
|
||||
decode: self.decode,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -184,13 +190,16 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> {
|
||||
parse_single_value!(deserialize_f32, visit_f32, "f32");
|
||||
parse_single_value!(deserialize_f64, visit_f64, "f64");
|
||||
parse_single_value!(deserialize_string, visit_string, "String");
|
||||
parse_single_value!(deserialize_str, visit_string, "String");
|
||||
parse_single_value!(deserialize_byte_buf, visit_string, "String");
|
||||
parse_single_value!(deserialize_char, visit_char, "char");
|
||||
|
||||
}
|
||||
|
||||
struct ParamsDeserializer<'de> {
|
||||
params: ParamsIter<'de>,
|
||||
current: Option<(&'de str, &'de str)>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||
@@ -212,7 +221,7 @@ impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> {
|
||||
V: de::DeserializeSeed<'de>,
|
||||
{
|
||||
if let Some((_, value)) = self.current.take() {
|
||||
seed.deserialize(Value { value })
|
||||
seed.deserialize(Value { value, decode: self.decode })
|
||||
} else {
|
||||
Err(de::value::Error::custom("unexpected item"))
|
||||
}
|
||||
@@ -252,16 +261,18 @@ macro_rules! parse_value {
|
||||
fn $trait_fn<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||
where V: Visitor<'de>
|
||||
{
|
||||
let v = self.value.parse().map_err(
|
||||
|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", self.value, $tp)))?;
|
||||
visitor.$visit_fn(v)
|
||||
let v_parsed = percent_decode_if_needed!(&self.value, self.decode)
|
||||
.map_err(|_| de::value::Error::custom(
|
||||
format!("can not parse {:?} to a {}", &self.value, $tp)
|
||||
))?;
|
||||
visitor.$visit_fn(v_parsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Value<'de> {
|
||||
value: &'de str,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de> Deserializer<'de> for Value<'de> {
|
||||
@@ -377,6 +388,7 @@ impl<'de> Deserializer<'de> for Value<'de> {
|
||||
|
||||
struct ParamsSeq<'de> {
|
||||
params: ParamsIter<'de>,
|
||||
decode: bool,
|
||||
}
|
||||
|
||||
impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
|
||||
@@ -387,7 +399,7 @@ impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> {
|
||||
T: de::DeserializeSeed<'de>,
|
||||
{
|
||||
match self.params.next() {
|
||||
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1 })?)),
|
||||
Some(item) => Ok(Some(seed.deserialize(Value { value: item.1, decode: self.decode })?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
312
src/error.rs
312
src/error.rs
@@ -759,6 +759,16 @@ where
|
||||
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PAYMENT_REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorPaymentRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PAYMENT_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *FORBIDDEN*
|
||||
/// response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -789,6 +799,26 @@ where
|
||||
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *NOT
|
||||
/// ACCEPTABLE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNotAcceptable<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::NOT_ACCEPTABLE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *PROXY
|
||||
/// AUTHENTICATION REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorProxyAuthenticationRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PROXY_AUTHENTICATION_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *REQUEST
|
||||
/// TIMEOUT* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -819,6 +849,16 @@ where
|
||||
InternalError::new(err, StatusCode::GONE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *LENGTH
|
||||
/// REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorLengthRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::LENGTH_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PRECONDITION FAILED* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -829,6 +869,46 @@ where
|
||||
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PAYLOAD TOO LARGE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorPayloadTooLarge<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PAYLOAD_TOO_LARGE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *URI TOO LONG* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUriTooLong<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::URI_TOO_LONG).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UNSUPPORTED MEDIA TYPE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUnsupportedMediaType<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UNSUPPORTED_MEDIA_TYPE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *RANGE NOT SATISFIABLE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorRangeNotSatisfiable<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::RANGE_NOT_SATISFIABLE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *EXPECTATION FAILED* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -839,6 +919,106 @@ where
|
||||
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *IM A TEAPOT* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorImATeapot<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::IM_A_TEAPOT).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *MISDIRECTED REQUEST* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorMisdirectedRequest<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::MISDIRECTED_REQUEST).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UNPROCESSABLE ENTITY* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUnprocessableEntity<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UNPROCESSABLE_ENTITY).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *LOCKED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorLocked<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::LOCKED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *FAILED DEPENDENCY* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorFailedDependency<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::FAILED_DEPENDENCY).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UPGRADE REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUpgradeRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UPGRADE_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *PRECONDITION REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorPreconditionRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::PRECONDITION_REQUIRED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *TOO MANY REQUESTS* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorTooManyRequests<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::TOO_MANY_REQUESTS).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *REQUEST HEADER FIELDS TOO LARGE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorRequestHeaderFieldsTooLarge<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate
|
||||
/// *UNAVAILABLE FOR LEGAL REASONS* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUnavailableForLegalReasons<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *INTERNAL SERVER ERROR* response.
|
||||
#[allow(non_snake_case)]
|
||||
@@ -889,6 +1069,66 @@ where
|
||||
InternalError::new(err, StatusCode::GATEWAY_TIMEOUT).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *HTTP VERSION NOT SUPPORTED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorHttpVersionNotSupported<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::HTTP_VERSION_NOT_SUPPORTED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *VARIANT ALSO NEGOTIATES* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorVariantAlsoNegotiates<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::VARIANT_ALSO_NEGOTIATES).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *INSUFFICIENT STORAGE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorInsufficientStorage<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::INSUFFICIENT_STORAGE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *LOOP DETECTED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorLoopDetected<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::LOOP_DETECTED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *NOT EXTENDED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNotExtended<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::NOT_EXTENDED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *NETWORK AUTHENTICATION REQUIRED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNetworkAuthenticationRequired<T>(err: T) -> Error
|
||||
where
|
||||
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
|
||||
{
|
||||
InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1068,6 +1308,9 @@ mod tests {
|
||||
let r: HttpResponse = ErrorUnauthorized("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
let r: HttpResponse = ErrorPaymentRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PAYMENT_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorForbidden("err").into();
|
||||
assert_eq!(r.status(), StatusCode::FORBIDDEN);
|
||||
|
||||
@@ -1077,6 +1320,12 @@ mod tests {
|
||||
let r: HttpResponse = ErrorMethodNotAllowed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
|
||||
let r: HttpResponse = ErrorNotAcceptable("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NOT_ACCEPTABLE);
|
||||
|
||||
let r: HttpResponse = ErrorProxyAuthenticationRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PROXY_AUTHENTICATION_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorRequestTimeout("err").into();
|
||||
assert_eq!(r.status(), StatusCode::REQUEST_TIMEOUT);
|
||||
|
||||
@@ -1086,12 +1335,57 @@ mod tests {
|
||||
let r: HttpResponse = ErrorGone("err").into();
|
||||
assert_eq!(r.status(), StatusCode::GONE);
|
||||
|
||||
let r: HttpResponse = ErrorLengthRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::LENGTH_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorPreconditionFailed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PRECONDITION_FAILED);
|
||||
|
||||
let r: HttpResponse = ErrorPayloadTooLarge("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PAYLOAD_TOO_LARGE);
|
||||
|
||||
let r: HttpResponse = ErrorUriTooLong("err").into();
|
||||
assert_eq!(r.status(), StatusCode::URI_TOO_LONG);
|
||||
|
||||
let r: HttpResponse = ErrorUnsupportedMediaType("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
|
||||
let r: HttpResponse = ErrorRangeNotSatisfiable("err").into();
|
||||
assert_eq!(r.status(), StatusCode::RANGE_NOT_SATISFIABLE);
|
||||
|
||||
let r: HttpResponse = ErrorExpectationFailed("err").into();
|
||||
assert_eq!(r.status(), StatusCode::EXPECTATION_FAILED);
|
||||
|
||||
let r: HttpResponse = ErrorImATeapot("err").into();
|
||||
assert_eq!(r.status(), StatusCode::IM_A_TEAPOT);
|
||||
|
||||
let r: HttpResponse = ErrorMisdirectedRequest("err").into();
|
||||
assert_eq!(r.status(), StatusCode::MISDIRECTED_REQUEST);
|
||||
|
||||
let r: HttpResponse = ErrorUnprocessableEntity("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
|
||||
let r: HttpResponse = ErrorLocked("err").into();
|
||||
assert_eq!(r.status(), StatusCode::LOCKED);
|
||||
|
||||
let r: HttpResponse = ErrorFailedDependency("err").into();
|
||||
assert_eq!(r.status(), StatusCode::FAILED_DEPENDENCY);
|
||||
|
||||
let r: HttpResponse = ErrorUpgradeRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UPGRADE_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorPreconditionRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::PRECONDITION_REQUIRED);
|
||||
|
||||
let r: HttpResponse = ErrorTooManyRequests("err").into();
|
||||
assert_eq!(r.status(), StatusCode::TOO_MANY_REQUESTS);
|
||||
|
||||
let r: HttpResponse = ErrorRequestHeaderFieldsTooLarge("err").into();
|
||||
assert_eq!(r.status(), StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE);
|
||||
|
||||
let r: HttpResponse = ErrorUnavailableForLegalReasons("err").into();
|
||||
assert_eq!(r.status(), StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS);
|
||||
|
||||
let r: HttpResponse = ErrorInternalServerError("err").into();
|
||||
assert_eq!(r.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
@@ -1106,5 +1400,23 @@ mod tests {
|
||||
|
||||
let r: HttpResponse = ErrorGatewayTimeout("err").into();
|
||||
assert_eq!(r.status(), StatusCode::GATEWAY_TIMEOUT);
|
||||
|
||||
let r: HttpResponse = ErrorHttpVersionNotSupported("err").into();
|
||||
assert_eq!(r.status(), StatusCode::HTTP_VERSION_NOT_SUPPORTED);
|
||||
|
||||
let r: HttpResponse = ErrorVariantAlsoNegotiates("err").into();
|
||||
assert_eq!(r.status(), StatusCode::VARIANT_ALSO_NEGOTIATES);
|
||||
|
||||
let r: HttpResponse = ErrorInsufficientStorage("err").into();
|
||||
assert_eq!(r.status(), StatusCode::INSUFFICIENT_STORAGE);
|
||||
|
||||
let r: HttpResponse = ErrorLoopDetected("err").into();
|
||||
assert_eq!(r.status(), StatusCode::LOOP_DETECTED);
|
||||
|
||||
let r: HttpResponse = ErrorNotExtended("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NOT_EXTENDED);
|
||||
|
||||
let r: HttpResponse = ErrorNetworkAuthenticationRequired("err").into();
|
||||
assert_eq!(r.status(), StatusCode::NETWORK_AUTHENTICATION_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
@@ -18,7 +18,8 @@ use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
|
||||
use httprequest::HttpRequest;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from the request's path.
|
||||
/// Extract typed information from the request's path. Information from the path is
|
||||
/// URL decoded. Decoding of special characters can be disabled through `PathConfig`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@@ -119,7 +120,7 @@ where
|
||||
let req = req.clone();
|
||||
let req2 = req.clone();
|
||||
let err = Rc::clone(&cfg.ehandler);
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&req))
|
||||
de::Deserialize::deserialize(PathDeserializer::new(&req, cfg.decode))
|
||||
.map_err(move |e| (*err)(e, &req2))
|
||||
.map(|inner| Path { inner })
|
||||
}
|
||||
@@ -149,6 +150,7 @@ where
|
||||
/// ```
|
||||
pub struct PathConfig<S> {
|
||||
ehandler: Rc<Fn(serde_urlencoded::de::Error, &HttpRequest<S>) -> Error>,
|
||||
decode: bool,
|
||||
}
|
||||
impl<S> PathConfig<S> {
|
||||
/// Set custom error handler
|
||||
@@ -159,12 +161,20 @@ impl<S> PathConfig<S> {
|
||||
self.ehandler = Rc::new(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable decoding of URL encoded special charaters from the path
|
||||
pub fn disable_decoding(&mut self) -> &mut Self
|
||||
{
|
||||
self.decode = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Default for PathConfig<S> {
|
||||
fn default() -> Self {
|
||||
PathConfig {
|
||||
ehandler: Rc::new(|e, _| ErrorNotFound(e)),
|
||||
decode: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1090,6 +1100,68 @@ mod tests {
|
||||
assert_eq!(*Path::<i8>::from_request(&req, &&PathConfig::default()).unwrap(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_decode() {
|
||||
let mut router = Router::<()>::default();
|
||||
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
||||
|
||||
macro_rules! test_single_value {
|
||||
($value:expr, $expected:expr) => {
|
||||
{
|
||||
let req = TestRequest::with_uri($value).finish();
|
||||
let info = router.recognize(&req, &(), 0);
|
||||
let req = req.with_route_info(info);
|
||||
assert_eq!(*Path::<String>::from_request(&req, &PathConfig::default()).unwrap(), $expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test_single_value!("/%25/", "%");
|
||||
test_single_value!("/%40%C2%A3%24%25%5E%26%2B%3D/", "@£$%^&+=");
|
||||
test_single_value!("/%2B/", "+");
|
||||
test_single_value!("/%252B/", "%2B");
|
||||
test_single_value!("/%2F/", "/");
|
||||
test_single_value!("/%252F/", "%2F");
|
||||
test_single_value!("/http%3A%2F%2Flocalhost%3A80%2Ffoo/", "http://localhost:80/foo");
|
||||
test_single_value!("/%2Fvar%2Flog%2Fsyslog/", "/var/log/syslog");
|
||||
test_single_value!(
|
||||
"/http%3A%2F%2Flocalhost%3A80%2Ffile%2F%252Fvar%252Flog%252Fsyslog/",
|
||||
"http://localhost:80/file/%2Fvar%2Flog%2Fsyslog"
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("/%25/7/?id=test").finish();
|
||||
|
||||
let mut router = Router::<()>::default();
|
||||
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
|
||||
let info = router.recognize(&req, &(), 0);
|
||||
let req = req.with_route_info(info);
|
||||
|
||||
let s = Path::<Test2>::from_request(&req, &PathConfig::default()).unwrap();
|
||||
assert_eq!(s.key, "%");
|
||||
assert_eq!(s.value, 7);
|
||||
|
||||
let s = Path::<(String, String)>::from_request(&req, &PathConfig::default()).unwrap();
|
||||
assert_eq!(s.0, "%");
|
||||
assert_eq!(s.1, "7");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_path_no_decode() {
|
||||
let mut router = Router::<()>::default();
|
||||
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
|
||||
|
||||
let req = TestRequest::with_uri("/%25/").finish();
|
||||
let info = router.recognize(&req, &(), 0);
|
||||
let req = req.with_route_info(info);
|
||||
assert_eq!(
|
||||
*Path::<String>::from_request(
|
||||
&req,
|
||||
&&PathConfig::default().disable_decoding()
|
||||
).unwrap(),
|
||||
"%25"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_extract() {
|
||||
let mut router = Router::<()>::default();
|
||||
|
@@ -250,7 +250,7 @@ pub(crate) enum AsyncResultItem<I, E> {
|
||||
impl<I, E> AsyncResult<I, E> {
|
||||
/// Create async response
|
||||
#[inline]
|
||||
pub fn async(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
|
||||
pub fn future(fut: Box<Future<Item = I, Error = E>>) -> AsyncResult<I, E> {
|
||||
AsyncResult(Some(AsyncResultItem::Future(fut)))
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ where
|
||||
},
|
||||
Err(e) => err(e),
|
||||
});
|
||||
Ok(AsyncResult::async(Box::new(fut)))
|
||||
Ok(AsyncResult::future(Box::new(fut)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +502,7 @@ where
|
||||
Err(e) => Either::A(err(e)),
|
||||
}
|
||||
});
|
||||
AsyncResult::async(Box::new(fut))
|
||||
AsyncResult::future(Box::new(fut))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -255,7 +255,7 @@ pub mod dev {
|
||||
|
||||
pub use body::BodyStream;
|
||||
pub use context::Drain;
|
||||
pub use extractor::{FormConfig, PayloadConfig};
|
||||
pub use extractor::{FormConfig, PayloadConfig, QueryConfig, PathConfig};
|
||||
pub use handler::{AsyncResult, Handler};
|
||||
pub use httpmessage::{MessageBody, Readlines, UrlEncoded};
|
||||
pub use httpresponse::HttpResponseBuilder;
|
||||
|
@@ -76,7 +76,7 @@ impl ResponseError for CsrfError {
|
||||
}
|
||||
|
||||
fn uri_origin(uri: &Uri) -> Option<String> {
|
||||
match (uri.scheme_part(), uri.host(), uri.port()) {
|
||||
match (uri.scheme_part(), uri.host(), uri.port_part().map(|port| port.as_u16())) {
|
||||
(Some(scheme), Some(host), Some(port)) => {
|
||||
Some(format!("{}://{}:{}", scheme, host, port))
|
||||
}
|
||||
|
33
src/param.rs
33
src/param.rs
@@ -8,7 +8,7 @@ use http::StatusCode;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use error::{InternalError, ResponseError, UriSegmentError};
|
||||
use uri::Url;
|
||||
use uri::{Url, RESERVED_QUOTER};
|
||||
|
||||
/// A trait to abstract the idea of creating a new instance of a type from a
|
||||
/// path parameter.
|
||||
@@ -103,6 +103,17 @@ impl Params {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get URL-decoded matched parameter by name without type conversion
|
||||
pub fn get_decoded(&self, key: &str) -> Option<String> {
|
||||
self.get(key).map(|value| {
|
||||
if let Some(ref mut value) = RESERVED_QUOTER.requote(value.as_bytes()) {
|
||||
Rc::make_mut(value).to_string()
|
||||
} else {
|
||||
value.to_string()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Get unprocessed part of path
|
||||
pub fn unprocessed(&self) -> &str {
|
||||
&self.url.path()[(self.tail as usize)..]
|
||||
@@ -300,4 +311,24 @@ mod tests {
|
||||
Ok(PathBuf::from_iter(vec!["seg2"]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_param_by_name() {
|
||||
let mut params = Params::new();
|
||||
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"), Some("path".to_string()));
|
||||
assert_eq!(
|
||||
params.get("item2"),
|
||||
Some("http%3A%2F%2Flocalhost%3A80%2Ffoo")
|
||||
);
|
||||
assert_eq!(
|
||||
params.get_decoded("item2"),
|
||||
Some("http://localhost:80/foo".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -57,7 +57,7 @@ impl<S: 'static> Route<S> {
|
||||
pub(crate) fn compose(
|
||||
&self, req: HttpRequest<S>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
||||
) -> AsyncResult<HttpResponse> {
|
||||
AsyncResult::async(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||
AsyncResult::future(Box::new(Compose::new(req, mws, self.handler.clone())))
|
||||
}
|
||||
|
||||
/// Add match predicate to route.
|
||||
|
@@ -356,7 +356,7 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
||||
if self.middlewares.is_empty() {
|
||||
self.router.handle(&req2)
|
||||
} else {
|
||||
AsyncResult::async(Box::new(Compose::new(
|
||||
AsyncResult::future(Box::new(Compose::new(
|
||||
req2,
|
||||
Rc::clone(&self.router),
|
||||
Rc::clone(&self.middlewares),
|
||||
|
95
src/uri.rs
95
src/uri.rs
@@ -1,25 +1,12 @@
|
||||
use http::Uri;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
||||
#[allow(dead_code)]
|
||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
||||
#[allow(dead_code)]
|
||||
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";
|
||||
// https://tools.ietf.org/html/rfc3986#section-2.2
|
||||
const RESERVED_PLUS_EXTRA: &[u8] = b":/?#[]@!$&'()*,+?;=%^ <>\"\\`{}|";
|
||||
|
||||
// https://tools.ietf.org/html/rfc3986#section-2.3
|
||||
const UNRESERVED: &[u8] =
|
||||
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-._~";
|
||||
|
||||
#[inline]
|
||||
fn bit_at(array: &[u8], ch: u8) -> bool {
|
||||
@@ -32,7 +19,8 @@ fn set_bit(array: &mut [u8], ch: u8) {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref DEFAULT_QUOTER: Quoter = { Quoter::new(b"@:", b"/+") };
|
||||
static ref UNRESERVED_QUOTER: Quoter = { Quoter::new(UNRESERVED) };
|
||||
pub(crate) static ref RESERVED_QUOTER: Quoter = { Quoter::new(RESERVED_PLUS_EXTRA) };
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
@@ -43,7 +31,7 @@ pub(crate) struct Url {
|
||||
|
||||
impl Url {
|
||||
pub fn new(uri: Uri) -> Url {
|
||||
let path = DEFAULT_QUOTER.requote(uri.path().as_bytes());
|
||||
let path = UNRESERVED_QUOTER.requote(uri.path().as_bytes());
|
||||
|
||||
Url { uri, path }
|
||||
}
|
||||
@@ -63,36 +51,19 @@ impl Url {
|
||||
|
||||
pub(crate) struct Quoter {
|
||||
safe_table: [u8; 16],
|
||||
protected_table: [u8; 16],
|
||||
}
|
||||
|
||||
impl Quoter {
|
||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
||||
pub fn new(safe: &[u8]) -> Quoter {
|
||||
let mut q = Quoter {
|
||||
safe_table: [0; 16],
|
||||
protected_table: [0; 16],
|
||||
};
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -115,19 +86,17 @@ impl Quoter {
|
||||
|
||||
if let Some(ch) = restore_ch(pct[1], pct[2]) {
|
||||
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) {
|
||||
buf.push(ch);
|
||||
idx += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
buf.extend_from_slice(&pct);
|
||||
} else {
|
||||
// Not ASCII, decode it
|
||||
buf.push(ch);
|
||||
}
|
||||
buf.push(ch);
|
||||
} else {
|
||||
buf.extend_from_slice(&pct[..]);
|
||||
}
|
||||
@@ -172,3 +141,37 @@ fn from_hex(v: u8) -> Option<u8> {
|
||||
fn restore_ch(d1: u8, d2: u8) -> Option<u8> {
|
||||
from_hex(d1).and_then(|d1| from_hex(d2).and_then(move |d2| Some(d1 << 4 | d2)))
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn decode_path() {
|
||||
assert_eq!(UNRESERVED_QUOTER.requote(b"https://localhost:80/foo"), None);
|
||||
|
||||
assert_eq!(
|
||||
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||
b"https://localhost:80/foo%25"
|
||||
).unwrap()).unwrap(),
|
||||
"https://localhost:80/foo%25".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||
b"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo"
|
||||
).unwrap()).unwrap(),
|
||||
"http://cache-service/http%3A%2F%2Flocalhost%3A80%2Ffoo".to_string()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Rc::try_unwrap(UNRESERVED_QUOTER.requote(
|
||||
b"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A"
|
||||
).unwrap()).unwrap(),
|
||||
"http://cache/http%3A%2F%2Flocal%3A80%2Ffile%2F%252Fvar%252Flog%0A".to_string()
|
||||
);
|
||||
}
|
||||
}
|
@@ -86,7 +86,7 @@ where
|
||||
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ where
|
||||
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(resp)) => AsyncResult::ok(resp),
|
||||
Ok(Async::NotReady) => AsyncResult::async(Box::new(fut)),
|
||||
Ok(Async::NotReady) => AsyncResult::future(Box::new(fut)),
|
||||
Err(e) => AsyncResult::err(e),
|
||||
}
|
||||
}
|
||||
|
@@ -231,6 +231,13 @@ where
|
||||
pub fn handle(&self) -> SpawnHandle {
|
||||
self.inner.curr_handle()
|
||||
}
|
||||
|
||||
/// Set mailbox capacity
|
||||
///
|
||||
/// By default mailbox capacity is 16 messages.
|
||||
pub fn set_mailbox_capacity(&mut self, cap: usize) {
|
||||
self.inner.set_mailbox_capacity(cap)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A, S> WsWriter for WebsocketContext<A, S>
|
||||
|
@@ -179,7 +179,7 @@ fn test_client_gzip_encoding_large() {
|
||||
#[test]
|
||||
fn test_client_gzip_encoding_large_random() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(100_000)
|
||||
.collect::<String>();
|
||||
|
||||
@@ -247,7 +247,7 @@ fn test_client_brotli_encoding() {
|
||||
#[test]
|
||||
fn test_client_brotli_encoding_large_random() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(70_000)
|
||||
.collect::<String>();
|
||||
|
||||
@@ -309,7 +309,7 @@ fn test_client_deflate_encoding() {
|
||||
#[test]
|
||||
fn test_client_deflate_encoding_large_random() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.sample_iter(&rand::distributions::Alphanumeric)
|
||||
.take(70_000)
|
||||
.collect::<String>();
|
||||
|
||||
|
@@ -672,6 +672,6 @@ fn test_unsafe_path_route() {
|
||||
let bytes = srv.execute(response.body()).unwrap();
|
||||
assert_eq!(
|
||||
bytes,
|
||||
Bytes::from_static(b"success: http:%2F%2Fexample.com")
|
||||
Bytes::from_static(b"success: http%3A%2F%2Fexample.com")
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user