//! Payload/Bytes/String extractors
use std::str;
use actix_http::error::{Error, ErrorBadRequest, PayloadError};
use actix_http::HttpMessage;
use bytes::{Bytes, BytesMut};
use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding};
use futures::future::{err, Either, FutureResult};
use futures::{Future, Poll, Stream};
use mime::Mime;
use crate::extract::FromRequest;
use crate::http::header;
use crate::service::ServiceFromRequest;
/// Payload extractor returns request 's payload stream.
///
/// ## Example
///
/// ```rust
/// use futures::{Future, Stream};
/// use actix_web::{web, error, App, Error, HttpResponse};
///
/// /// extract binary data from request
/// fn index
(body: web::Payload
) -> impl Future-
/// where
/// P: Stream
-
/// {
/// body.map_err(Error::from)
/// .fold(web::BytesMut::new(), move |mut body, chunk| {
/// body.extend_from_slice(&chunk);
/// Ok::<_, Error>(body)
/// })
/// .and_then(|body| {
/// format!("Body {:?}!", body);
/// Ok(HttpResponse::Ok().finish())
/// })
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get().to_async(index))
/// );
/// }
/// ```
pub struct Payload
(crate::dev::Payload);
impl Stream for Payload
where
T: Stream- ,
{
type Item = Bytes;
type Error = PayloadError;
#[inline]
fn poll(&mut self) -> Poll
, PayloadError> {
self.0.poll()
}
}
/// Get request's payload stream
///
/// ## Example
///
/// ```rust
/// use futures::{Future, Stream};
/// use actix_web::{web, error, App, Error, HttpResponse};
///
/// /// extract binary data from request
/// fn index(body: web::Payload
) -> impl Future-
/// where
/// P: Stream
-
/// {
/// body.map_err(Error::from)
/// .fold(web::BytesMut::new(), move |mut body, chunk| {
/// body.extend_from_slice(&chunk);
/// Ok::<_, Error>(body)
/// })
/// .and_then(|body| {
/// format!("Body {:?}!", body);
/// Ok(HttpResponse::Ok().finish())
/// })
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get().to_async(index))
/// );
/// }
/// ```
impl
FromRequest
for Payload
where
P: Stream- ,
{
type Error = Error;
type Future = Result
, Error>;
#[inline]
fn from_request(req: &mut ServiceFromRequest) -> Self::Future {
Ok(Payload(req.take_payload()))
}
}
/// Request binary data from a request's payload.
///
/// Loads request's payload and construct Bytes instance.
///
/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure
/// extraction process.
///
/// ## Example
///
/// ```rust
/// use bytes::Bytes;
/// use actix_web::{web, App};
///
/// /// extract binary data from request
/// fn index(body: Bytes) -> String {
/// format!("Body {:?}!", body)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get().to(index))
/// );
/// }
/// ```
impl
FromRequest
for Bytes
where
P: Stream- + 'static,
{
type Error = Error;
type Future =
Either
>, FutureResult>;
#[inline]
fn from_request(req: &mut ServiceFromRequest) -> Self::Future {
let mut tmp;
let cfg = if let Some(cfg) = req.route_data::() {
cfg
} else {
tmp = PayloadConfig::default();
&tmp
};
if let Err(e) = cfg.check_mimetype(req) {
return Either::B(err(e));
}
let limit = cfg.limit;
Either::A(Box::new(HttpMessageBody::new(req).limit(limit).from_err()))
}
}
/// Extract text information from a request's body.
///
/// Text extractor automatically decode body according to the request's charset.
///
/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure
/// extraction process.
///
/// ## Example
///
/// ```rust
/// use actix_web::{web, App};
///
/// /// extract text data from request
/// fn index(text: String) -> String {
/// format!("Body {}!", text)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(
/// web::get()
/// .data(web::PayloadConfig::new(4096)) // <- limit size of the payload
/// .to(index)) // <- register handler with extractor params
/// );
/// }
/// ```
impl FromRequest
for String
where
P: Stream- + 'static,
{
type Error = Error;
type Future =
Either
>, FutureResult>;
#[inline]
fn from_request(req: &mut ServiceFromRequest) -> Self::Future {
let mut tmp;
let cfg = if let Some(cfg) = req.route_data::() {
cfg
} else {
tmp = PayloadConfig::default();
&tmp
};
// check content-type
if let Err(e) = cfg.check_mimetype(req) {
return Either::B(err(e));
}
// check charset
let encoding = match req.encoding() {
Ok(enc) => enc,
Err(e) => return Either::B(err(e.into())),
};
let limit = cfg.limit;
Either::A(Box::new(
HttpMessageBody::new(req)
.limit(limit)
.from_err()
.and_then(move |body| {
let enc: *const Encoding = encoding as *const Encoding;
if enc == UTF_8 {
Ok(str::from_utf8(body.as_ref())
.map_err(|_| ErrorBadRequest("Can not decode body"))?
.to_owned())
} else {
Ok(encoding
.decode(&body, DecoderTrap::Strict)
.map_err(|_| ErrorBadRequest("Can not decode body"))?)
}
}),
))
}
}
/// Payload configuration for request's payload.
#[derive(Clone)]
pub struct PayloadConfig {
limit: usize,
mimetype: Option,
}
impl PayloadConfig {
/// Create `PayloadConfig` instance and set max size of payload.
pub fn new(limit: usize) -> Self {
Self::default().limit(limit)
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
/// Set required mime-type of the request. By default mime type is not
/// enforced.
pub fn mimetype(mut self, mt: Mime) -> Self {
self.mimetype = Some(mt);
self
}
fn check_mimetype(&self, req: &ServiceFromRequest
) -> Result<(), Error> {
// check content-type
if let Some(ref mt) = self.mimetype {
match req.mime_type() {
Ok(Some(ref req_mt)) => {
if mt != req_mt {
return Err(ErrorBadRequest("Unexpected Content-Type"));
}
}
Ok(None) => {
return Err(ErrorBadRequest("Content-Type is expected"));
}
Err(err) => {
return Err(err.into());
}
}
}
Ok(())
}
}
impl Default for PayloadConfig {
fn default() -> Self {
PayloadConfig {
limit: 262_144,
mimetype: None,
}
}
}
/// Future that resolves to a complete http message body.
///
/// Load http message body.
///
/// By default only 256Kb payload reads to a memory, then
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
/// method to change upper limit.
pub struct HttpMessageBody {
limit: usize,
length: Option,
stream: actix_http::Payload,
err: Option,
fut: Option>>,
}
impl HttpMessageBody
where
T: HttpMessage,
T::Stream: Stream- ,
{
/// Create `MessageBody` for request.
pub fn new(req: &mut T) -> HttpMessageBody
{
let mut len = None;
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = l.to_str() {
if let Ok(l) = s.parse::() {
len = Some(l)
} else {
return Self::err(PayloadError::UnknownLength);
}
} else {
return Self::err(PayloadError::UnknownLength);
}
}
HttpMessageBody {
stream: req.take_payload(),
limit: 262_144,
length: len,
fut: None,
err: None,
}
}
/// Change max size of payload. By default max size is 256Kb
pub fn limit(mut self, limit: usize) -> Self {
self.limit = limit;
self
}
fn err(e: PayloadError) -> Self {
HttpMessageBody {
stream: actix_http::Payload::None,
limit: 262_144,
fut: None,
err: Some(e),
length: None,
}
}
}
impl Future for HttpMessageBody
where
T: HttpMessage,
T::Stream: Stream- + 'static,
{
type Item = Bytes;
type Error = PayloadError;
fn poll(&mut self) -> Poll
{
if let Some(ref mut fut) = self.fut {
return fut.poll();
}
if let Some(err) = self.err.take() {
return Err(err);
}
if let Some(len) = self.length.take() {
if len > self.limit {
return Err(PayloadError::Overflow);
}
}
// future
let limit = self.limit;
self.fut = Some(Box::new(
std::mem::replace(&mut self.stream, actix_http::Payload::None)
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(PayloadError::Overflow)
} else {
body.extend_from_slice(&chunk);
Ok(body)
}
})
.map(|body| body.freeze()),
));
self.poll()
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use super::*;
use crate::http::header;
use crate::test::{block_on, TestRequest};
#[test]
fn test_payload_config() {
let req = TestRequest::default().to_from();
let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON);
assert!(cfg.check_mimetype(&req).is_err());
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.to_from();
assert!(cfg.check_mimetype(&req).is_err());
let req =
TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from();
assert!(cfg.check_mimetype(&req).is_ok());
}
#[test]
fn test_bytes() {
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.to_from();
let s = block_on(Bytes::from_request(&mut req)).unwrap();
assert_eq!(s, Bytes::from_static(b"hello=world"));
}
#[test]
fn test_string() {
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.to_from();
let s = block_on(String::from_request(&mut req)).unwrap();
assert_eq!(s, "hello=world");
}
#[test]
fn test_message_body() {
let mut req =
TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").to_request();
let res = block_on(HttpMessageBody::new(&mut req));
match res.err().unwrap() {
PayloadError::UnknownLength => (),
_ => unreachable!("error"),
}
let mut req =
TestRequest::with_header(header::CONTENT_LENGTH, "1000000").to_request();
let res = block_on(HttpMessageBody::new(&mut req));
match res.err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
let mut req = TestRequest::default()
.set_payload(Bytes::from_static(b"test"))
.to_request();
let res = block_on(HttpMessageBody::new(&mut req));
assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test"));
let mut req = TestRequest::default()
.set_payload(Bytes::from_static(b"11111111111111"))
.to_request();
let res = block_on(HttpMessageBody::new(&mut req).limit(5));
match res.err().unwrap() {
PayloadError::Overflow => (),
_ => unreachable!("error"),
}
}
}