1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-20 08:31:09 +00:00

add multipart and urlencoded bodies support

This commit is contained in:
Nikolay Kim 2017-10-17 16:46:57 -07:00
parent fb92d5552d
commit 264380bf33
3 changed files with 156 additions and 11 deletions

View file

@ -42,6 +42,7 @@ regex = "0.2"
slab = "0.4"
sha1 = "0.2"
url = "1.5"
multipart-async = { version = "0.*", features=["server"]}
# tokio
bytes = "0.4"

View file

@ -1,12 +1,18 @@
//! HTTP Request message related code.
use std::str;
use std::{io, str};
use std::collections::HashMap;
use bytes::{Bytes, BytesMut};
use futures::{Async, Future, Stream, Poll};
use url::form_urlencoded;
use multipart_async::server::BodyChunk;
use http::{header, Method, Version, Uri, HeaderMap};
use {Cookie, CookieParseError};
use {HttpRange, HttpRangeParseError};
use error::ParseError;
use recognizer::Params;
use multipart::Multipart;
use payload::{Payload, PayloadError};
#[derive(Debug)]
@ -52,15 +58,6 @@ impl HttpRequest {
&self.headers
}
// /// The remote socket address of this request
// ///
// /// This is an `Option`, because some underlying transports may not have
// /// a socket address, such as Unix Sockets.
// ///
// /// This field is not used for outgoing requests.
// #[inline]
// pub fn remote_addr(&self) -> Option<SocketAddr> { self.remote_addr }
/// The target path of this Request.
#[inline]
pub fn path(&self) -> &str {
@ -178,4 +175,144 @@ impl HttpRequest {
Ok(Vec::new())
}
}
/// Return stream to process BODY as multipart.
///
/// Content-type: multipart/form-data;
pub fn multipart(&self, payload: Payload) -> Result<Multipart<Req>, Payload> {
const BOUNDARY: &'static str = "boundary=";
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if let Some(start) = content_type.find(BOUNDARY) {
let start = start + BOUNDARY.len();
let end = content_type[start..].find(';')
.map_or(content_type.len(), |end| start + end);
let boundary = &content_type[start .. end];
return Ok(Multipart::with_body(Req{pl: payload}, boundary))
}
}
}
Err(payload)
}
/// Parse `application/x-www-form-urlencoded` encoded body.
/// Return `UrlEncoded` future. It resolves to a `HashMap<String, String>`.
///
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
pub fn urlencoded(&self, payload: Payload) -> Result<UrlEncoded, Payload> {
if let Ok(chunked) = self.chunked() {
if chunked {
return Err(payload)
}
}
if let Some(len) = self.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
return Err(payload)
}
} else {
return Err(payload)
}
} else {
return Err(payload)
}
}
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
if content_type.to_lowercase() == "application/x-www-form-urlencoded" {
return Ok(UrlEncoded{pl: payload, body: BytesMut::new()})
}
}
}
Err(payload)
}
}
#[doc(hidden)]
pub struct Req {
pl: Payload,
}
#[doc(hidden)]
pub struct Chunk(Bytes);
impl BodyChunk for Chunk {
#[inline]
fn split_at(mut self, idx: usize) -> (Self, Self) {
(Chunk(self.0.split_to(idx)), self)
}
#[inline]
fn as_slice(&self) -> &[u8] {
self.0.as_ref()
}
}
impl Stream for Req {
type Item = Chunk;
type Error = io::Error;
fn poll(&mut self) -> Poll<Option<Chunk>, io::Error> {
match self.pl.poll() {
Err(_) =>
Err(io::Error::new(io::ErrorKind::InvalidData, "incomplete")),
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
Ok(Async::Ready(Some(item))) => match item {
Ok(bytes) => Ok(Async::Ready(Some(Chunk(bytes)))),
Err(err) => match err {
PayloadError::Incomplete =>
Err(io::Error::new(io::ErrorKind::InvalidData, "incomplete")),
PayloadError::ParseError(err) =>
Err(err.into())
}
}
}
}
}
/// Future that resolves to a parsed urlencoded values.
pub struct UrlEncoded {
pl: Payload,
body: BytesMut,
}
impl Future for UrlEncoded {
type Item = HashMap<String, String>;
type Error = PayloadError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
return match self.pl.poll() {
Err(_) => unreachable!(),
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => {
let mut m = HashMap::new();
for (k, v) in form_urlencoded::parse(&self.body) {
m.insert(k.into(), v.into());
}
Ok(Async::Ready(m))
},
Ok(Async::Ready(Some(item))) => match item {
Ok(bytes) => {
self.body.extend(bytes);
continue
},
Err(err) => Err(err),
}
}
}
}
}

View file

@ -21,6 +21,7 @@ extern crate http;
extern crate httparse;
extern crate http_range;
extern crate mime_guess;
extern crate multipart_async;
extern crate url;
extern crate actix;
@ -48,7 +49,7 @@ pub mod dev;
pub mod httpcodes;
pub use error::ParseError;
pub use application::{Application, ApplicationBuilder};
pub use httprequest::HttpRequest;
pub use httprequest::{HttpRequest, UrlEncoded};
pub use httpresponse::{Body, HttpResponse, HttpResponseBuilder};
pub use payload::{Payload, PayloadItem, PayloadError};
pub use router::{Router, RoutingMap};
@ -64,3 +65,9 @@ pub use http::{Method, StatusCode};
pub use cookie::{Cookie, CookieBuilder};
pub use cookie::{ParseError as CookieParseError};
pub use http_range::{HttpRange, HttpRangeParseError};
/// Multipart support
pub mod multipart {
pub use multipart_async::server::{
Field, FieldData, FieldHeaders, Multipart, ReadTextField, TextField};
}