Merge remote-tracking branch 'github/master' into bump_json_limits

This commit is contained in:
peter-formlogic 2021-05-05 07:25:29 +09:30
commit 39bf19bcc0
No known key found for this signature in database
GPG Key ID: CE3C608698F6E66B
18 changed files with 315 additions and 94 deletions

View File

@ -5,6 +5,7 @@
* Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162] * Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162]
[#2162]: (https://github.com/actix/actix-web/pull/2162) [#2162]: (https://github.com/actix/actix-web/pull/2162)
* Update `language-tags` to `0.3`.
## 4.0.0-beta.6 - 2021-04-17 ## 4.0.0-beta.6 - 2021-04-17

View File

@ -78,7 +78,7 @@ encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
itoa = "0.4" itoa = "0.4"
language-tags = "0.2" language-tags = "0.3"
once_cell = "1.5" once_cell = "1.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"

View File

@ -11,6 +11,9 @@
## 0.6.0-beta.4 - 2021-04-02 ## 0.6.0-beta.4 - 2021-04-02
* No notable changes. * No notable changes.
* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046]
[#2046]: https://github.com/actix/actix-web/pull/2046
## 0.6.0-beta.3 - 2021-03-09 ## 0.6.0-beta.3 - 2021-03-09
* No notable changes. * No notable changes.

View File

@ -37,7 +37,8 @@ pub struct Files {
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
guards: Option<Rc<dyn Guard>>, use_guards: Option<Rc<dyn Guard>>,
guards: Vec<Box<Rc<dyn Guard>>>,
hidden_files: bool, hidden_files: bool,
} }
@ -59,12 +60,12 @@ impl Clone for Files {
file_flags: self.file_flags, file_flags: self.file_flags,
path: self.path.clone(), path: self.path.clone(),
mime_override: self.mime_override.clone(), mime_override: self.mime_override.clone(),
use_guards: self.use_guards.clone(),
guards: self.guards.clone(), guards: self.guards.clone(),
hidden_files: self.hidden_files, hidden_files: self.hidden_files,
} }
} }
} }
impl Files { impl Files {
/// Create new `Files` instance for a specified base directory. /// Create new `Files` instance for a specified base directory.
/// ///
@ -80,10 +81,9 @@ impl Files {
/// If the mount path is set as the root path `/`, services registered after this one will /// If the mount path is set as the root path `/`, services registered after this one will
/// be inaccessible. Register more specific handlers and services first. /// be inaccessible. Register more specific handlers and services first.
/// ///
/// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a /// `Files` utilizes the existing Tokio thread-pool for blocking filesystem operations.
/// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are /// The number of running threads is adjusted over time as needed, up to a maximum of 512 times
/// adjusted with work load. More threads would spawn when need and threads goes idle for a /// the number of server [workers](HttpServer::workers), by default.
/// period of time would be de-spawned.
pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files { pub fn new<T: Into<PathBuf>>(mount_path: &str, serve_from: T) -> Files {
let orig_dir = serve_from.into(); let orig_dir = serve_from.into();
let dir = match orig_dir.canonicalize() { let dir = match orig_dir.canonicalize() {
@ -104,7 +104,8 @@ impl Files {
renderer: Rc::new(directory_listing), renderer: Rc::new(directory_listing),
mime_override: None, mime_override: None,
file_flags: named::Flags::default(), file_flags: named::Flags::default(),
guards: None, use_guards: None,
guards: Vec::new(),
hidden_files: false, hidden_files: false,
} }
} }
@ -156,7 +157,6 @@ impl Files {
/// Specifies whether to use ETag or not. /// Specifies whether to use ETag or not.
/// ///
/// Default is true. /// Default is true.
#[inline]
pub fn use_etag(mut self, value: bool) -> Self { pub fn use_etag(mut self, value: bool) -> Self {
self.file_flags.set(named::Flags::ETAG, value); self.file_flags.set(named::Flags::ETAG, value);
self self
@ -165,7 +165,6 @@ impl Files {
/// Specifies whether to use Last-Modified or not. /// Specifies whether to use Last-Modified or not.
/// ///
/// Default is true. /// Default is true.
#[inline]
pub fn use_last_modified(mut self, value: bool) -> Self { pub fn use_last_modified(mut self, value: bool) -> Self {
self.file_flags.set(named::Flags::LAST_MD, value); self.file_flags.set(named::Flags::LAST_MD, value);
self self
@ -174,25 +173,55 @@ impl Files {
/// Specifies whether text responses should signal a UTF-8 encoding. /// Specifies whether text responses should signal a UTF-8 encoding.
/// ///
/// Default is false (but will default to true in a future version). /// Default is false (but will default to true in a future version).
#[inline]
pub fn prefer_utf8(mut self, value: bool) -> Self { pub fn prefer_utf8(mut self, value: bool) -> Self {
self.file_flags.set(named::Flags::PREFER_UTF8, value); self.file_flags.set(named::Flags::PREFER_UTF8, value);
self self
} }
/// Specifies custom guards to use for directory listings and files. /// Adds a routing guard.
/// ///
/// Default behaviour allows GET and HEAD. /// Use this to allow multiple chained file services that respond to strictly different
#[inline] /// properties of a request. Due to the way routing works, if a guard check returns true and the
pub fn use_guards<G: Guard + 'static>(mut self, guards: G) -> Self { /// request starts being handled by the file service, it will not be able to back-out and try
self.guards = Some(Rc::new(guards)); /// the next service, you will simply get a 404 (or 405) error response.
///
/// To allow `POST` requests to retrieve files, see [`Files::use_guards`].
///
/// # Examples
/// ```
/// use actix_web::{guard::Header, App};
/// use actix_files::Files;
///
/// App::new().service(
/// Files::new("/","/my/site/files")
/// .guard(Header("Host", "example.com"))
/// );
/// ```
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.guards.push(Box::new(Rc::new(guard)));
self self
} }
/// Specifies guard to check before fetching directory listings or files.
///
/// Note that this guard has no effect on routing; it's main use is to guard on the request's
/// method just before serving the file, only allowing `GET` and `HEAD` requests by default.
/// See [`Files::guard`] for routing guards.
pub fn method_guard<G: Guard + 'static>(mut self, guard: G) -> Self {
self.use_guards = Some(Rc::new(guard));
self
}
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")]
/// See [`Files::method_guard`].
pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
self.method_guard(guard)
}
/// Disable `Content-Disposition` header. /// Disable `Content-Disposition` header.
/// ///
/// By default Content-Disposition` header is enabled. /// By default Content-Disposition` header is enabled.
#[inline]
pub fn disable_content_disposition(mut self) -> Self { pub fn disable_content_disposition(mut self) -> Self {
self.file_flags.remove(named::Flags::CONTENT_DISPOSITION); self.file_flags.remove(named::Flags::CONTENT_DISPOSITION);
self self
@ -200,8 +229,9 @@ impl Files {
/// Sets default handler which is used when no matched file could be found. /// Sets default handler which is used when no matched file could be found.
/// ///
/// For example, you could set a fall back static file handler: /// # Examples
/// ```rust /// Setting a fallback static file handler:
/// ```
/// use actix_files::{Files, NamedFile}; /// use actix_files::{Files, NamedFile};
/// ///
/// # fn run() -> Result<(), actix_web::Error> { /// # fn run() -> Result<(), actix_web::Error> {
@ -230,7 +260,6 @@ impl Files {
} }
/// Enables serving hidden files and directories, allowing a leading dots in url fragments. /// Enables serving hidden files and directories, allowing a leading dots in url fragments.
#[inline]
pub fn use_hidden_files(mut self) -> Self { pub fn use_hidden_files(mut self) -> Self {
self.hidden_files = true; self.hidden_files = true;
self self
@ -238,7 +267,19 @@ impl Files {
} }
impl HttpServiceFactory for Files { impl HttpServiceFactory for Files {
fn register(self, config: &mut AppService) { fn register(mut self, config: &mut AppService) {
let guards = if self.guards.is_empty() {
None
} else {
let guards = std::mem::take(&mut self.guards);
Some(
guards
.into_iter()
.map(|guard| -> Box<dyn Guard> { guard })
.collect::<Vec<_>>(),
)
};
if self.default.borrow().is_none() { if self.default.borrow().is_none() {
*self.default.borrow_mut() = Some(config.default_service()); *self.default.borrow_mut() = Some(config.default_service());
} }
@ -249,7 +290,7 @@ impl HttpServiceFactory for Files {
ResourceDef::prefix(&self.path) ResourceDef::prefix(&self.path)
}; };
config.register_service(rdef, None, self, None) config.register_service(rdef, guards, self, None)
} }
} }
@ -271,7 +312,7 @@ impl ServiceFactory<ServiceRequest> for Files {
renderer: self.renderer.clone(), renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(), mime_override: self.mime_override.clone(),
file_flags: self.file_flags, file_flags: self.file_flags,
guards: self.guards.clone(), guards: self.use_guards.clone(),
hidden_files: self.hidden_files, hidden_files: self.hidden_files,
}; };

View File

@ -532,7 +532,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_files_guards() { async fn test_files_guards() {
let srv = test::init_service( let srv = test::init_service(
App::new().service(Files::new("/", ".").use_guards(guard::Post())), App::new().service(Files::new("/", ".").method_guard(guard::Post())),
) )
.await; .await;

View File

@ -0,0 +1 @@
first

View File

@ -0,0 +1 @@
second

View File

@ -0,0 +1,36 @@
use actix_files::Files;
use actix_web::{
guard::Host,
http::StatusCode,
test::{self, TestRequest},
App,
};
use bytes::Bytes;
#[actix_rt::test]
async fn test_guard_filter() {
let srv = test::init_service(
App::new()
.service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com")))
.service(
Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")),
),
)
.await;
let req = TestRequest::with_uri("/index.txt")
.append_header(("Host", "first.com"))
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(test::read_body(res).await, Bytes::from("first"));
let req = TestRequest::with_uri("/index.txt")
.append_header(("Host", "second.com"))
.to_request();
let res = test::call_service(&srv, req).await;
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(test::read_body(res).await, Bytes::from("second"));
}

View File

@ -9,11 +9,14 @@
### Changed ### Changed
* `header` mod is now public. [#2171] * `header` mod is now public. [#2171]
* `uri` mod is now public. [#2171] * `uri` mod is now public. [#2171]
* Update `language-tags` to `0.3`.
* Reduce the level from `error` to `debug` for the log line that is emitted when a `500 Internal Server Error` is built using `HttpResponse::from_error`. [#2196]
### Removed ### Removed
* Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171] * Stop re-exporting `http` crate's `HeaderMap` types in addition to ours. [#2171]
[#2171]: https://github.com/actix/actix-web/pull/2171 [#2171]: https://github.com/actix/actix-web/pull/2171
[#2196]: https://github.com/actix/actix-web/pull/2196
## 3.0.0-beta.6 - 2021-04-17 ## 3.0.0-beta.6 - 2021-04-17

View File

@ -57,7 +57,7 @@ h2 = "0.3.1"
http = "0.2.2" http = "0.2.2"
httparse = "1.3" httparse = "1.3"
itoa = "0.4" itoa = "0.4"
language-tags = "0.2" language-tags = "0.3"
local-channel = "0.1" local-channel = "0.1"
once_cell = "1.5" once_cell = "1.5"
log = "0.4" log = "0.4"

View File

@ -78,7 +78,7 @@ impl Response<Body> {
pub fn from_error(error: Error) -> Response<Body> { pub fn from_error(error: Error) -> Response<Body> {
let mut resp = error.as_response_error().error_response(); let mut resp = error.as_response_error().error_response();
if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR {
error!("Internal Server Error: {:?}", error); debug!("Internal Server Error: {:?}", error);
} }
resp.error = Some(error); resp.error = Some(error);
resp resp

View File

@ -86,13 +86,17 @@ impl ResponseError for UrlencodedError {
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[non_exhaustive] #[non_exhaustive]
pub enum JsonPayloadError { pub enum JsonPayloadError {
/// Payload size is bigger than allowed. (default: 2MB) /// Payload size is bigger than allowed & content length header set. (default: 2MB)
#[display( #[display(
fmt = "JSON payload ({} bytes) is larger than allowed (limit: {} bytes).", fmt = "JSON payload ({} bytes) is larger than allowed (limit: {} bytes).",
size, length,
limit limit
)] )]
Overflow { size: usize, limit: usize }, OverflowKnownLength { length: usize, limit: usize },
/// Payload size is bigger than allowed but no content length header set. (default: 2MB)
#[display(fmt = "JSON payload has exceeded limit ({} bytes).", limit)]
Overflow { limit: usize },
/// Content type error /// Content type error
#[display(fmt = "Content type error")] #[display(fmt = "Content type error")]
@ -120,7 +124,11 @@ impl From<PayloadError> for JsonPayloadError {
impl ResponseError for JsonPayloadError { impl ResponseError for JsonPayloadError {
fn status_code(&self) -> StatusCode { fn status_code(&self) -> StatusCode {
match self { match self {
Self::Overflow { size: _, limit: _ } => StatusCode::PAYLOAD_TOO_LARGE, Self::OverflowKnownLength {
length: _,
limit: _,
} => StatusCode::PAYLOAD_TOO_LARGE,
Self::Overflow { limit: _ } => StatusCode::PAYLOAD_TOO_LARGE,
Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR, Self::Serialize(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::Payload(err) => err.status_code(), Self::Payload(err) => err.status_code(),
_ => StatusCode::BAD_REQUEST, _ => StatusCode::BAD_REQUEST,
@ -207,7 +215,13 @@ mod tests {
#[test] #[test]
fn test_json_payload_error() { fn test_json_payload_error() {
let resp = JsonPayloadError::Overflow { size: 0, limit: 0 }.error_response(); let resp = JsonPayloadError::OverflowKnownLength {
length: 0,
limit: 0,
}
.error_response();
assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE);
let resp = JsonPayloadError::Overflow { limit: 0 }.error_response();
assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE); assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE);
let resp = JsonPayloadError::ContentType.error_response(); let resp = JsonPayloadError::ContentType.error_response();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST); assert_eq!(resp.status(), StatusCode::BAD_REQUEST);

View File

@ -26,6 +26,8 @@
//! ``` //! ```
#![allow(non_snake_case)] #![allow(non_snake_case)]
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ops::Deref;
use std::rc::Rc;
use actix_http::http::{self, header, uri::Uri}; use actix_http::http::{self, header, uri::Uri};
use actix_http::RequestHead; use actix_http::RequestHead;
@ -40,6 +42,12 @@ pub trait Guard {
fn check(&self, request: &RequestHead) -> bool; fn check(&self, request: &RequestHead) -> bool;
} }
impl Guard for Rc<dyn Guard> {
fn check(&self, request: &RequestHead) -> bool {
self.deref().check(request)
}
}
/// Create guard object for supplied function. /// Create guard object for supplied function.
/// ///
/// ``` /// ```

View File

@ -24,14 +24,11 @@ crate::__define_common_header! {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use language_tags::langtag;
/// use actix_web::HttpResponse; /// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem}; /// use actix_web::http::header::{AcceptLanguage, LanguageTag, qitem};
/// ///
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// let mut langtag: LanguageTag = Default::default(); /// let langtag = LanguageTag::parse("en-US").unwrap();
/// langtag.language = Some("en".to_owned());
/// langtag.region = Some("US".to_owned());
/// builder.insert_header( /// builder.insert_header(
/// AcceptLanguage(vec![ /// AcceptLanguage(vec![
/// qitem(langtag), /// qitem(langtag),
@ -40,16 +37,15 @@ crate::__define_common_header! {
/// ``` /// ```
/// ///
/// ``` /// ```
/// use language_tags::langtag;
/// use actix_web::HttpResponse; /// use actix_web::HttpResponse;
/// use actix_web::http::header::{AcceptLanguage, QualityItem, q, qitem}; /// use actix_web::http::header::{AcceptLanguage, LanguageTag, QualityItem, q, qitem};
/// ///
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// AcceptLanguage(vec![ /// AcceptLanguage(vec![
/// qitem(langtag!(da)), /// qitem(LanguageTag::parse("da").unwrap()),
/// QualityItem::new(langtag!(en;;;GB), q(800)), /// QualityItem::new(LanguageTag::parse("en-GB").unwrap(), q(800)),
/// QualityItem::new(langtag!(en), q(700)), /// QualityItem::new(LanguageTag::parse("en").unwrap(), q(700)),
/// ]) /// ])
/// ); /// );
/// ``` /// ```

View File

@ -24,28 +24,26 @@ crate::__define_common_header! {
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use language_tags::langtag;
/// use actix_web::HttpResponse; /// use actix_web::HttpResponse;
/// use actix_web::http::header::{ContentLanguage, qitem}; /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem};
/// ///
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// ContentLanguage(vec![ /// ContentLanguage(vec![
/// qitem(langtag!(en)), /// qitem(LanguageTag::parse("en").unwrap()),
/// ]) /// ])
/// ); /// );
/// ``` /// ```
/// ///
/// ``` /// ```
/// use language_tags::langtag;
/// use actix_web::HttpResponse; /// use actix_web::HttpResponse;
/// use actix_web::http::header::{ContentLanguage, qitem}; /// use actix_web::http::header::{ContentLanguage, LanguageTag, qitem};
/// ///
/// let mut builder = HttpResponse::Ok(); /// let mut builder = HttpResponse::Ok();
/// builder.insert_header( /// builder.insert_header(
/// ContentLanguage(vec![ /// ContentLanguage(vec![
/// qitem(langtag!(da)), /// qitem(LanguageTag::parse("da").unwrap()),
/// qitem(langtag!(en;;;GB)), /// qitem(LanguageTag::parse("en-GB").unwrap()),
/// ]) /// ])
/// ); /// );
/// ``` /// ```

View File

@ -1,8 +1,14 @@
//! For either helper, see [`Either`]. //! For either helper, see [`Either`].
use std::{
future::Future,
mem,
pin::Pin,
task::{Context, Poll},
};
use bytes::Bytes; use bytes::Bytes;
use futures_core::future::LocalBoxFuture; use futures_core::ready;
use futures_util::{FutureExt as _, TryFutureExt as _};
use crate::{ use crate::{
dev, dev,
@ -180,41 +186,111 @@ where
R: FromRequest + 'static, R: FromRequest + 'static,
{ {
type Error = EitherExtractError<L::Error, R::Error>; type Error = EitherExtractError<L::Error, R::Error>;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>; type Future = EitherExtractFut<L, R>;
type Config = (); type Config = ();
fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future {
let req2 = req.clone(); EitherExtractFut {
req: req.clone(),
Bytes::from_request(req, payload) state: EitherExtractState::Bytes {
.map_err(EitherExtractError::Bytes) bytes: Bytes::from_request(req, payload),
.and_then(|bytes| bytes_to_l_or_r(req2, bytes)) },
.boxed_local() }
} }
} }
async fn bytes_to_l_or_r<L, R>( #[pin_project::pin_project]
req: HttpRequest, pub struct EitherExtractFut<L, R>
bytes: Bytes,
) -> Result<Either<L, R>, EitherExtractError<L::Error, R::Error>>
where where
L: FromRequest + 'static, R: FromRequest,
R: FromRequest + 'static, L: FromRequest,
{ {
let fallback = bytes.clone(); req: HttpRequest,
let a_err; #[pin]
state: EitherExtractState<L, R>,
}
let mut pl = payload_from_bytes(bytes); #[pin_project::pin_project(project = EitherExtractProj)]
match L::from_request(&req, &mut pl).await { pub enum EitherExtractState<L, R>
Ok(a_data) => return Ok(Either::Left(a_data)), where
// store A's error for returning if B also fails L: FromRequest,
Err(err) => a_err = err, R: FromRequest,
}; {
Bytes {
#[pin]
bytes: <Bytes as FromRequest>::Future,
},
Left {
#[pin]
left: L::Future,
fallback: Bytes,
},
Right {
#[pin]
right: R::Future,
left_err: Option<L::Error>,
},
}
let mut pl = payload_from_bytes(fallback); impl<R, RF, RE, L, LF, LE> Future for EitherExtractFut<L, R>
match R::from_request(&req, &mut pl).await { where
Ok(b_data) => return Ok(Either::Right(b_data)), L: FromRequest<Future = LF, Error = LE>,
Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)), R: FromRequest<Future = RF, Error = RE>,
LF: Future<Output = Result<L, LE>> + 'static,
RF: Future<Output = Result<R, RE>> + 'static,
LE: Into<Error>,
RE: Into<Error>,
{
type Output = Result<Either<L, R>, EitherExtractError<LE, RE>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
let ready = loop {
let next = match this.state.as_mut().project() {
EitherExtractProj::Bytes { bytes } => {
let res = ready!(bytes.poll(cx));
match res {
Ok(bytes) => {
let fallback = bytes.clone();
let left =
L::from_request(&this.req, &mut payload_from_bytes(bytes));
EitherExtractState::Left { left, fallback }
}
Err(err) => break Err(EitherExtractError::Bytes(err)),
}
}
EitherExtractProj::Left { left, fallback } => {
let res = ready!(left.poll(cx));
match res {
Ok(extracted) => break Ok(Either::Left(extracted)),
Err(left_err) => {
let right = R::from_request(
&this.req,
&mut payload_from_bytes(mem::take(fallback)),
);
EitherExtractState::Right {
left_err: Some(left_err),
right,
}
}
}
}
EitherExtractProj::Right { right, left_err } => {
let res = ready!(right.poll(cx));
match res {
Ok(data) => break Ok(Either::Right(data)),
Err(err) => {
break Err(EitherExtractError::Extract(
left_err.take().unwrap(),
err,
));
}
}
}
};
this.state.set(next);
};
Poll::Ready(ready)
} }
} }

View File

@ -12,7 +12,7 @@ use std::{
use actix_http::Payload; use actix_http::Payload;
use bytes::BytesMut; use bytes::BytesMut;
use encoding_rs::{Encoding, UTF_8}; use encoding_rs::{Encoding, UTF_8};
use futures_core::future::LocalBoxFuture; use futures_core::{future::LocalBoxFuture, ready};
use futures_util::{FutureExt as _, StreamExt as _}; use futures_util::{FutureExt as _, StreamExt as _};
use serde::{de::DeserializeOwned, Serialize}; use serde::{de::DeserializeOwned, Serialize};
@ -123,11 +123,10 @@ where
{ {
type Config = FormConfig; type Config = FormConfig;
type Error = Error; type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self, Error>>; type Future = FormExtractFut<T>;
#[inline] #[inline]
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
let req2 = req.clone();
let (limit, err_handler) = req let (limit, err_handler) = req
.app_data::<Self::Config>() .app_data::<Self::Config>()
.or_else(|| { .or_else(|| {
@ -137,16 +136,42 @@ where
.map(|c| (c.limit, c.err_handler.clone())) .map(|c| (c.limit, c.err_handler.clone()))
.unwrap_or((16384, None)); .unwrap_or((16384, None));
UrlEncoded::new(req, payload) FormExtractFut {
.limit(limit) fut: UrlEncoded::new(req, payload).limit(limit),
.map(move |res| match res { req: req.clone(),
Err(err) => match err_handler { err_handler,
Some(err_handler) => Err((err_handler)(err, &req2)), }
None => Err(err.into()), }
}, }
Ok(item) => Ok(Form(item)),
}) type FormErrHandler = Option<Rc<dyn Fn(UrlencodedError, &HttpRequest) -> Error>>;
.boxed_local()
pub struct FormExtractFut<T> {
fut: UrlEncoded<T>,
err_handler: FormErrHandler,
req: HttpRequest,
}
impl<T> Future for FormExtractFut<T>
where
T: DeserializeOwned + 'static,
{
type Output = Result<Form<T>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.get_mut();
let res = ready!(Pin::new(&mut this.fut).poll(cx));
let res = match res {
Err(err) => match &this.err_handler {
Some(err_handler) => Err((err_handler)(err, &this.req)),
None => Err(err.into()),
},
Ok(item) => Ok(Form(item)),
};
Poll::Ready(res)
} }
} }
@ -193,7 +218,7 @@ impl<T: Serialize> Responder for Form<T> {
#[derive(Clone)] #[derive(Clone)]
pub struct FormConfig { pub struct FormConfig {
limit: usize, limit: usize,
err_handler: Option<Rc<dyn Fn(UrlencodedError, &HttpRequest) -> Error>>, err_handler: FormErrHandler,
} }
impl FormConfig { impl FormConfig {

View File

@ -370,8 +370,8 @@ where
} => { } => {
if let Some(len) = length { if let Some(len) = length {
if len > limit { if len > limit {
return JsonBody::Error(Some(JsonPayloadError::Overflow { return JsonBody::Error(Some(JsonPayloadError::OverflowKnownLength {
size: len, length: len,
limit, limit,
})); }));
} }
@ -404,7 +404,6 @@ where
limit, limit,
buf, buf,
payload, payload,
length,
.. ..
} => loop { } => loop {
let res = ready!(Pin::new(&mut *payload).poll_next(cx)); let res = ready!(Pin::new(&mut *payload).poll_next(cx));
@ -414,7 +413,6 @@ where
let buf_len = buf.len() + chunk.len(); let buf_len = buf.len() + chunk.len();
if buf_len > *limit { if buf_len > *limit {
return Poll::Ready(Err(JsonPayloadError::Overflow { return Poll::Ready(Err(JsonPayloadError::Overflow {
size: length.unwrap_or(buf_len),
limit: *limit, limit: *limit,
})); }));
} else { } else {
@ -458,6 +456,9 @@ mod tests {
JsonPayloadError::Overflow { .. } => { JsonPayloadError::Overflow { .. } => {
matches!(other, JsonPayloadError::Overflow { .. }) matches!(other, JsonPayloadError::Overflow { .. })
} }
JsonPayloadError::OverflowKnownLength { .. } => {
matches!(other, JsonPayloadError::OverflowKnownLength { .. })
}
JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType), JsonPayloadError::ContentType => matches!(other, JsonPayloadError::ContentType),
_ => false, _ => false,
} }
@ -603,12 +604,29 @@ mod tests {
.await; .await;
assert!(json_eq( assert!(json_eq(
json.err().unwrap(), json.err().unwrap(),
JsonPayloadError::Overflow { JsonPayloadError::OverflowKnownLength {
size: 10000, length: 10000,
limit: 100 limit: 100
} }
)); ));
let (req, mut pl) = TestRequest::default()
.insert_header((
header::CONTENT_TYPE,
header::HeaderValue::from_static("application/json"),
))
.set_payload(Bytes::from_static(&[0u8; 1000]))
.to_http_parts();
let json = JsonBody::<MyObject>::new(&req, &mut pl, None)
.limit(100)
.await;
assert!(json_eq(
json.err().unwrap(),
JsonPayloadError::Overflow { limit: 100 }
));
let (req, mut pl) = TestRequest::default() let (req, mut pl) = TestRequest::default()
.insert_header(( .insert_header((
header::CONTENT_TYPE, header::CONTENT_TYPE,