mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-23 23:51:06 +01:00
update multipart impl
This commit is contained in:
parent
aaef550bc5
commit
24804250a8
@ -50,8 +50,8 @@ tokio-proto = "0.1"
|
|||||||
|
|
||||||
[dependencies.actix]
|
[dependencies.actix]
|
||||||
# path = "../actix"
|
# path = "../actix"
|
||||||
#git = "https://github.com/fafhrd91/actix.git"
|
git = "https://github.com/fafhrd91/actix.git"
|
||||||
version = "0.2"
|
#version = "0.2"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = []
|
features = []
|
||||||
|
|
||||||
|
@ -9,5 +9,6 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
actix = "0.2"
|
# actix = "0.2"
|
||||||
|
actix = { git = "https://github.com/fafhrd91/actix.git" }
|
||||||
actix-web = { path = "../../" }
|
actix-web = { path = "../../" }
|
||||||
|
@ -2,7 +2,7 @@ import asyncio
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
def client():
|
def req1():
|
||||||
with aiohttp.MultipartWriter() as writer:
|
with aiohttp.MultipartWriter() as writer:
|
||||||
writer.append('test')
|
writer.append('test')
|
||||||
writer.append_json({'passed': True})
|
writer.append_json({'passed': True})
|
||||||
@ -14,5 +14,19 @@ def client():
|
|||||||
assert 200 == resp.status
|
assert 200 == resp.status
|
||||||
|
|
||||||
|
|
||||||
|
def req2():
|
||||||
|
with aiohttp.MultipartWriter() as writer:
|
||||||
|
writer.append('test')
|
||||||
|
writer.append_json({'passed': True})
|
||||||
|
writer.append(open('src/main.rs'))
|
||||||
|
|
||||||
|
resp = yield from aiohttp.request(
|
||||||
|
"post", 'http://localhost:8080/multipart',
|
||||||
|
data=writer, headers=writer.headers)
|
||||||
|
print(resp)
|
||||||
|
assert 200 == resp.status
|
||||||
|
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
loop.run_until_complete(client())
|
loop.run_until_complete(req1())
|
||||||
|
loop.run_until_complete(req2())
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#![allow(unused_variables)]
|
||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
@ -16,39 +17,44 @@ impl Route for MyRoute {
|
|||||||
|
|
||||||
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self> {
|
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self> {
|
||||||
println!("{:?}", req);
|
println!("{:?}", req);
|
||||||
match req.multipart(payload) {
|
|
||||||
Ok(multipart) => {
|
// get Multipart stream
|
||||||
ctx.add_stream(multipart);
|
WrapStream::<MyRoute>::actstream(req.multipart(payload)?)
|
||||||
Reply::async(MyRoute)
|
.and_then(|item, act, ctx| {
|
||||||
|
// Multipart stream is a string of Fields and nested Multiparts
|
||||||
|
match item {
|
||||||
|
multipart::MultipartItem::Field(field) => {
|
||||||
|
println!("==== FIELD ==== {:?}", field);
|
||||||
|
|
||||||
|
// Read field's stream
|
||||||
|
fut::Either::A(
|
||||||
|
field.actstream()
|
||||||
|
.map(|chunk, act, ctx| {
|
||||||
|
println!(
|
||||||
|
"-- CHUNK: \n{}",
|
||||||
|
std::str::from_utf8(&chunk.0).unwrap());
|
||||||
|
})
|
||||||
|
.finish())
|
||||||
},
|
},
|
||||||
// can not read multipart
|
multipart::MultipartItem::Nested(mp) => {
|
||||||
Err(_) => {
|
// Do nothing for nested multipart stream
|
||||||
Reply::reply(httpcodes::HTTPBadRequest)
|
fut::Either::B(fut::ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
// wait until stream finish
|
||||||
|
.finish()
|
||||||
impl ResponseType<multipart::MultipartItem> for MyRoute {
|
.map_err(|e, act, ctx| {
|
||||||
type Item = ();
|
ctx.start(httpcodes::HTTPBadRequest);
|
||||||
type Error = ();
|
ctx.write_eof();
|
||||||
}
|
})
|
||||||
|
.map(|_, act, ctx| {
|
||||||
impl StreamHandler<multipart::MultipartItem, PayloadError> for MyRoute {
|
|
||||||
fn finished(&mut self, ctx: &mut Self::Context) {
|
|
||||||
println!("FINISHED");
|
|
||||||
ctx.start(httpcodes::HTTPOk);
|
ctx.start(httpcodes::HTTPOk);
|
||||||
ctx.write_eof();
|
ctx.write_eof();
|
||||||
}
|
})
|
||||||
}
|
.spawn(ctx);
|
||||||
|
|
||||||
impl Handler<multipart::MultipartItem, PayloadError> for MyRoute {
|
Reply::async(MyRoute)
|
||||||
fn handle(&mut self, msg: multipart::MultipartItem, ctx: &mut HttpContext<Self>)
|
|
||||||
-> Response<Self, multipart::MultipartItem>
|
|
||||||
{
|
|
||||||
println!("==== FIELD ==== {:?}", msg);
|
|
||||||
//if let Some(req) = self.req.take() {
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ use httparse;
|
|||||||
use http::{StatusCode, Error as HttpError};
|
use http::{StatusCode, Error as HttpError};
|
||||||
|
|
||||||
use HttpRangeParseError;
|
use HttpRangeParseError;
|
||||||
|
use multipart::MultipartError;
|
||||||
use httpresponse::{Body, HttpResponse};
|
use httpresponse::{Body, HttpResponse};
|
||||||
|
|
||||||
|
|
||||||
@ -131,6 +132,14 @@ impl From<cookie::ParseError> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `BadRequest` for `MultipartError`
|
||||||
|
impl From<MultipartError> for HttpResponse {
|
||||||
|
fn from(err: MultipartError) -> Self {
|
||||||
|
HttpResponse::new(StatusCode::BAD_REQUEST,
|
||||||
|
Body::Binary(err.description().into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `BadRequest` for `HttpRangeParseError`
|
/// Return `BadRequest` for `HttpRangeParseError`
|
||||||
impl From<HttpRangeParseError> for HttpResponse {
|
impl From<HttpRangeParseError> for HttpResponse {
|
||||||
fn from(_: HttpRangeParseError) -> Self {
|
fn from(_: HttpRangeParseError) -> Self {
|
||||||
|
@ -178,7 +178,7 @@ impl HttpRequest {
|
|||||||
///
|
///
|
||||||
/// Content-type: multipart/form-data;
|
/// Content-type: multipart/form-data;
|
||||||
pub fn multipart(&self, payload: Payload) -> Result<Multipart, MultipartError> {
|
pub fn multipart(&self, payload: Payload) -> Result<Multipart, MultipartError> {
|
||||||
Multipart::new(self, payload)
|
Ok(Multipart::new(Multipart::boundary(self)?, payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse `application/x-www-form-urlencoded` encoded body.
|
/// Parse `application/x-www-form-urlencoded` encoded body.
|
||||||
|
311
src/multipart.rs
311
src/multipart.rs
@ -2,6 +2,7 @@
|
|||||||
use std::{cmp, fmt};
|
use std::{cmp, fmt};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::error::Error;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use mime;
|
use mime;
|
||||||
@ -12,14 +13,68 @@ use http::header::{self, HeaderMap, HeaderName, HeaderValue};
|
|||||||
use futures::{Async, Stream, Poll};
|
use futures::{Async, Stream, Poll};
|
||||||
use futures::task::{Task, current as current_task};
|
use futures::task::{Task, current as current_task};
|
||||||
|
|
||||||
|
use error::ParseError;
|
||||||
use payload::{Payload, PayloadError};
|
use payload::{Payload, PayloadError};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
const MAX_HEADERS: usize = 32;
|
const MAX_HEADERS: usize = 32;
|
||||||
|
|
||||||
|
/// A set of errors that can occur during parsing multipart streams.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MultipartError {
|
pub enum MultipartError {
|
||||||
pub payload: Payload,
|
/// Content-Type header is not found
|
||||||
|
NoContentType,
|
||||||
|
/// Can not parse Content-Type header
|
||||||
|
ParseContentType,
|
||||||
|
/// Multipart boundary is not found
|
||||||
|
Boundary,
|
||||||
|
/// Error during field parsing
|
||||||
|
Parse(ParseError),
|
||||||
|
/// Payload error
|
||||||
|
Payload(PayloadError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MultipartError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
MultipartError::Parse(ref e) => fmt::Display::fmt(e, f),
|
||||||
|
MultipartError::Payload(ref e) => fmt::Display::fmt(e, f),
|
||||||
|
ref e => f.write_str(e.description()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for MultipartError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
MultipartError::NoContentType => "No Content-type header found",
|
||||||
|
MultipartError::ParseContentType => "Can not parse Content-Type header",
|
||||||
|
MultipartError::Boundary => "Multipart boundary is not found",
|
||||||
|
MultipartError::Parse(ref e) => e.description(),
|
||||||
|
MultipartError::Payload(ref e) => e.description(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&Error> {
|
||||||
|
match *self {
|
||||||
|
MultipartError::Parse(ref error) => Some(error),
|
||||||
|
MultipartError::Payload(ref error) => Some(error),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl From<ParseError> for MultipartError {
|
||||||
|
fn from(err: ParseError) -> MultipartError {
|
||||||
|
MultipartError::Parse(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PayloadError> for MultipartError {
|
||||||
|
fn from(err: PayloadError) -> MultipartError {
|
||||||
|
MultipartError::Payload(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The server-side implementation of `multipart/form-data` requests.
|
/// The server-side implementation of `multipart/form-data` requests.
|
||||||
@ -31,51 +86,95 @@ pub struct MultipartError {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Multipart {
|
pub struct Multipart {
|
||||||
safety: Safety,
|
safety: Safety,
|
||||||
payload: PayloadRef,
|
inner: Rc<RefCell<InnerMultipart>>,
|
||||||
boundary: String,
|
|
||||||
eof: bool,
|
|
||||||
bof: bool,
|
|
||||||
item: InnerMultipartItem,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum MultipartItem {
|
pub enum MultipartItem {
|
||||||
// Multipart field
|
/// Multipart field
|
||||||
Field(Field),
|
Field(Field),
|
||||||
// Nested multipart item
|
/// Nested multipart stream
|
||||||
Multipart(Multipart),
|
Nested(Multipart),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum InnerMultipartItem {
|
enum InnerMultipartItem {
|
||||||
None,
|
None,
|
||||||
Field(Rc<RefCell<InnerField>>),
|
Field(Rc<RefCell<InnerField>>),
|
||||||
// Nested multipart item
|
Multipart(Rc<RefCell<InnerMultipart>>),
|
||||||
// Multipart(Multipart),
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum InnerState {
|
||||||
|
/// Stream eof
|
||||||
|
Eof,
|
||||||
|
/// Skip data until first boundary
|
||||||
|
FirstBoundary,
|
||||||
|
/// Reading boundary
|
||||||
|
Boundary,
|
||||||
|
/// Reading Headers,
|
||||||
|
Headers,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InnerMultipart {
|
||||||
|
payload: PayloadRef,
|
||||||
|
boundary: String,
|
||||||
|
state: InnerState,
|
||||||
|
item: InnerMultipartItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Multipart {
|
impl Multipart {
|
||||||
pub fn new(req: &HttpRequest, payload: Payload) -> Result<Multipart, MultipartError> {
|
pub fn new(boundary: String, payload: Payload) -> Multipart {
|
||||||
|
Multipart {
|
||||||
|
safety: Safety::new(),
|
||||||
|
inner: Rc::new(RefCell::new(
|
||||||
|
InnerMultipart {
|
||||||
|
payload: PayloadRef::new(payload),
|
||||||
|
boundary: boundary,
|
||||||
|
state: InnerState::FirstBoundary,
|
||||||
|
item: InnerMultipartItem::None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boundary(req: &HttpRequest) -> Result<String, MultipartError> {
|
||||||
if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
|
if let Some(content_type) = req.headers().get(header::CONTENT_TYPE) {
|
||||||
if let Ok(content_type) = content_type.to_str() {
|
if let Ok(content_type) = content_type.to_str() {
|
||||||
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
||||||
if let Some(boundary) = ct.get_param(mime::BOUNDARY) {
|
if let Some(boundary) = ct.get_param(mime::BOUNDARY) {
|
||||||
return Ok(Multipart {
|
Ok(boundary.as_str().to_owned())
|
||||||
safety: Safety::new(),
|
} else {
|
||||||
payload: PayloadRef::new(payload),
|
Err(MultipartError::Boundary)
|
||||||
boundary: boundary.as_str().to_owned(),
|
}
|
||||||
eof: false,
|
} else {
|
||||||
bof: true,
|
Err(MultipartError::ParseContentType)
|
||||||
item: InnerMultipartItem::None,
|
}
|
||||||
})
|
} else {
|
||||||
|
Err(MultipartError::ParseContentType)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(MultipartError::NoContentType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(MultipartError{payload: payload})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_headers(payload: &mut Payload) -> Poll<HeaderMap, PayloadError>
|
impl Stream for Multipart {
|
||||||
|
type Item = MultipartItem;
|
||||||
|
type Error = MultipartError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
|
if self.safety.current() {
|
||||||
|
self.inner.borrow_mut().poll(&self.safety)
|
||||||
|
} else {
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerMultipart {
|
||||||
|
|
||||||
|
fn read_headers(payload: &mut Payload) -> Poll<HeaderMap, MultipartError>
|
||||||
{
|
{
|
||||||
match payload.readuntil(b"\r\n\r\n")? {
|
match payload.readuntil(b"\r\n\r\n")? {
|
||||||
Async::NotReady => Ok(Async::NotReady),
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
@ -90,21 +189,22 @@ impl Multipart {
|
|||||||
if let Ok(value) = HeaderValue::try_from(h.value) {
|
if let Ok(value) = HeaderValue::try_from(h.value) {
|
||||||
headers.append(name, value);
|
headers.append(name, value);
|
||||||
} else {
|
} else {
|
||||||
return Err(PayloadError::Incomplete)
|
return Err(ParseError::Header.into())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(PayloadError::Incomplete)
|
return Err(ParseError::Header.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(headers))
|
Ok(Async::Ready(headers))
|
||||||
}
|
}
|
||||||
Ok(httparse::Status::Partial) | Err(_) => Err(PayloadError::Incomplete),
|
Ok(httparse::Status::Partial) => Err(ParseError::Header.into()),
|
||||||
|
Err(err) => Err(ParseError::from(err).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, PayloadError>
|
fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError>
|
||||||
{
|
{
|
||||||
// TODO: need to read epilogue
|
// TODO: need to read epilogue
|
||||||
match payload.readline()? {
|
match payload.readline()? {
|
||||||
@ -122,13 +222,13 @@ impl Multipart {
|
|||||||
{
|
{
|
||||||
Ok(Async::Ready(true))
|
Ok(Async::Ready(true))
|
||||||
} else {
|
} else {
|
||||||
Err(PayloadError::Incomplete)
|
Err(MultipartError::Boundary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_until_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, PayloadError>
|
fn skip_until_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError>
|
||||||
{
|
{
|
||||||
let mut eof = false;
|
let mut eof = false;
|
||||||
loop {
|
loop {
|
||||||
@ -154,28 +254,29 @@ impl Multipart {
|
|||||||
}
|
}
|
||||||
Ok(Async::Ready(eof))
|
Ok(Async::Ready(eof))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Multipart {
|
fn poll(&mut self, safety: &Safety) -> Poll<Option<MultipartItem>, MultipartError> {
|
||||||
fn drop(&mut self) {
|
if self.state == InnerState::Eof {
|
||||||
// InnerMultipartItem::Field has to be dropped first because of Safety.
|
|
||||||
self.item = InnerMultipartItem::None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Stream for Multipart {
|
|
||||||
type Item = MultipartItem;
|
|
||||||
type Error = PayloadError;
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
|
||||||
if self.eof {
|
|
||||||
Ok(Async::Ready(None))
|
Ok(Async::Ready(None))
|
||||||
} else {
|
} else {
|
||||||
// release field
|
// release field
|
||||||
loop {
|
loop {
|
||||||
|
// Nested multipart streams of fields has to be consumed
|
||||||
|
// before switching to next
|
||||||
|
if safety.current() {
|
||||||
let stop = match self.item {
|
let stop = match self.item {
|
||||||
InnerMultipartItem::Field(ref mut field) => {
|
InnerMultipartItem::Field(ref mut field) => {
|
||||||
match field.borrow_mut().poll(&self.safety)? {
|
match field.borrow_mut().poll(safety)? {
|
||||||
|
Async::NotReady =>
|
||||||
|
return Ok(Async::NotReady),
|
||||||
|
Async::Ready(Some(_)) =>
|
||||||
|
continue,
|
||||||
|
Async::Ready(None) =>
|
||||||
|
true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InnerMultipartItem::Multipart(ref mut multipart) => {
|
||||||
|
match multipart.borrow_mut().poll(safety)? {
|
||||||
Async::NotReady =>
|
Async::NotReady =>
|
||||||
return Ok(Async::NotReady),
|
return Ok(Async::NotReady),
|
||||||
Async::Ready(Some(_)) =>
|
Async::Ready(Some(_)) =>
|
||||||
@ -193,51 +294,106 @@ impl Stream for Multipart {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let headers = if let Some(payload) = self.payload.get_mut(&self.safety) {
|
let headers = if let Some(payload) = self.payload.get_mut(safety) {
|
||||||
|
match self.state {
|
||||||
// read until first boundary
|
// read until first boundary
|
||||||
if self.bof {
|
InnerState::FirstBoundary => {
|
||||||
if let Async::Ready(eof) =
|
if let Async::Ready(eof) =
|
||||||
Multipart::skip_until_boundary(payload, &self.boundary)?
|
InnerMultipart::skip_until_boundary(payload, &self.boundary)?
|
||||||
{
|
{
|
||||||
self.eof = eof;
|
if eof {
|
||||||
|
self.state = InnerState::Eof;
|
||||||
|
return Ok(Async::Ready(None));
|
||||||
|
} else {
|
||||||
|
self.state = InnerState::Headers;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
self.bof = false;
|
}
|
||||||
} else {
|
|
||||||
// read boundary
|
// read boundary
|
||||||
match Multipart::read_boundary(payload, &self.boundary)? {
|
InnerState::Boundary => {
|
||||||
|
match InnerMultipart::read_boundary(payload, &self.boundary)? {
|
||||||
Async::NotReady => return Ok(Async::NotReady),
|
Async::NotReady => return Ok(Async::NotReady),
|
||||||
Async::Ready(eof) => self.eof = eof,
|
Async::Ready(eof) => {
|
||||||
|
if eof {
|
||||||
|
self.state = InnerState::Eof;
|
||||||
|
return Ok(Async::Ready(None));
|
||||||
|
} else {
|
||||||
|
self.state = InnerState::Headers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
if self.eof {
|
// read field headers for next field
|
||||||
return Ok(Async::Ready(None))
|
if self.state == InnerState::Headers {
|
||||||
}
|
if let Async::Ready(headers) = InnerMultipart::read_headers(payload)? {
|
||||||
|
self.state = InnerState::Boundary;
|
||||||
// read field headers
|
|
||||||
if let Async::Ready(headers) = Multipart::read_headers(payload)? {
|
|
||||||
headers
|
headers
|
||||||
} else {
|
} else {
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
debug!("NotReady: field is in flight");
|
debug!("NotReady: field is in flight");
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady)
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
// content type
|
||||||
|
let mut mt = mime::APPLICATION_OCTET_STREAM;
|
||||||
|
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
|
||||||
|
if let Ok(content_type) = content_type.to_str() {
|
||||||
|
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
||||||
|
mt = ct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nested multipart stream
|
||||||
|
if mt.type_() == mime::MULTIPART {
|
||||||
|
let inner = if let Some(boundary) = mt.get_param(mime::BOUNDARY) {
|
||||||
|
Rc::new(RefCell::new(
|
||||||
|
InnerMultipart {
|
||||||
|
payload: self.payload.clone(),
|
||||||
|
boundary: boundary.as_str().to_owned(),
|
||||||
|
state: InnerState::FirstBoundary,
|
||||||
|
item: InnerMultipartItem::None,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
return Err(MultipartError::Boundary)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.item = InnerMultipartItem::Multipart(Rc::clone(&inner));
|
||||||
|
|
||||||
|
Ok(Async::Ready(Some(
|
||||||
|
MultipartItem::Nested(
|
||||||
|
Multipart{safety: safety.clone(), inner: inner}))))
|
||||||
|
} else {
|
||||||
let field = Rc::new(RefCell::new(InnerField::new(
|
let field = Rc::new(RefCell::new(InnerField::new(
|
||||||
self.payload.clone(), self.boundary.clone(), &headers)?));
|
self.payload.clone(), self.boundary.clone(), &headers)?));
|
||||||
self.item = InnerMultipartItem::Field(Rc::clone(&field));
|
self.item = InnerMultipartItem::Field(Rc::clone(&field));
|
||||||
|
|
||||||
Ok(Async::Ready(Some(
|
Ok(Async::Ready(Some(
|
||||||
MultipartItem::Field(Field::new(self.safety.clone(), headers, field)))))
|
MultipartItem::Field(
|
||||||
|
Field::new(safety.clone(), headers, mt, field)))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for InnerMultipart {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// InnerMultipartItem::Field has to be dropped first because of Safety.
|
||||||
|
self.item = InnerMultipartItem::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A single field in a multipart stream
|
/// A single field in a multipart stream
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
@ -247,19 +403,16 @@ pub struct Field {
|
|||||||
safety: Safety,
|
safety: Safety,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A field's chunk
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub struct FieldChunk(pub Bytes);
|
||||||
|
|
||||||
impl Field {
|
impl Field {
|
||||||
|
|
||||||
fn new(safety: Safety, headers: HeaderMap, inner: Rc<RefCell<InnerField>>) -> Self {
|
fn new(safety: Safety, headers: HeaderMap,
|
||||||
let mut mt = mime::APPLICATION_OCTET_STREAM;
|
ct: mime::Mime, inner: Rc<RefCell<InnerField>>) -> Self {
|
||||||
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
|
|
||||||
if let Ok(content_type) = content_type.to_str() {
|
|
||||||
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
|
||||||
mt = ct;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Field {
|
Field {
|
||||||
ct: mt,
|
ct: ct,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
inner: inner,
|
inner: inner,
|
||||||
safety: safety,
|
safety: safety,
|
||||||
@ -276,8 +429,8 @@ impl Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for Field {
|
impl Stream for Field {
|
||||||
type Item = Bytes;
|
type Item = FieldChunk;
|
||||||
type Error = PayloadError;
|
type Error = MultipartError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
if self.safety.current() {
|
if self.safety.current() {
|
||||||
@ -341,7 +494,7 @@ impl InnerField {
|
|||||||
|
|
||||||
/// Reads body part content chunk of the specified size.
|
/// Reads body part content chunk of the specified size.
|
||||||
/// The body part must has `Content-Length` header with proper value.
|
/// The body part must has `Content-Length` header with proper value.
|
||||||
fn read_len(payload: &mut Payload, size: &mut u64) -> Poll<Option<Bytes>, PayloadError>
|
fn read_len(payload: &mut Payload, size: &mut u64) -> Poll<Option<Bytes>, MultipartError>
|
||||||
{
|
{
|
||||||
if *size == 0 {
|
if *size == 0 {
|
||||||
Ok(Async::Ready(None))
|
Ok(Async::Ready(None))
|
||||||
@ -358,14 +511,14 @@ impl InnerField {
|
|||||||
}
|
}
|
||||||
Ok(Async::Ready(Some(ch)))
|
Ok(Async::Ready(Some(ch)))
|
||||||
},
|
},
|
||||||
Async::Ready(Some(Err(err))) => Err(err)
|
Async::Ready(Some(Err(err))) => Err(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads content chunk of body part with unknown length.
|
/// Reads content chunk of body part with unknown length.
|
||||||
/// The `Content-Length` header for body part is not necessary.
|
/// The `Content-Length` header for body part is not necessary.
|
||||||
fn read_stream(payload: &mut Payload, boundary: &str) -> Poll<Option<Bytes>, PayloadError>
|
fn read_stream(payload: &mut Payload, boundary: &str) -> Poll<Option<Bytes>, MultipartError>
|
||||||
{
|
{
|
||||||
match payload.readuntil(b"\r")? {
|
match payload.readuntil(b"\r")? {
|
||||||
Async::NotReady => Ok(Async::NotReady),
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
@ -395,7 +548,7 @@ impl InnerField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(&mut self, s: &Safety) -> Poll<Option<Bytes>, PayloadError> {
|
fn poll(&mut self, s: &Safety) -> Poll<Option<FieldChunk>, MultipartError> {
|
||||||
if self.payload.is_none() {
|
if self.payload.is_none() {
|
||||||
return Ok(Async::Ready(None))
|
return Ok(Async::Ready(None))
|
||||||
}
|
}
|
||||||
@ -427,7 +580,7 @@ impl InnerField {
|
|||||||
|
|
||||||
match res {
|
match res {
|
||||||
Async::NotReady => Async::NotReady,
|
Async::NotReady => Async::NotReady,
|
||||||
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
|
Async::Ready(Some(bytes)) => Async::Ready(Some(FieldChunk(bytes))),
|
||||||
Async::Ready(None) => {
|
Async::Ready(None) => {
|
||||||
self.eof = true;
|
self.eof = true;
|
||||||
match payload.readline()? {
|
match payload.readline()? {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
use std::fmt;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
use std::error::Error;
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::{Async, Poll, Stream};
|
use futures::{Async, Poll, Stream};
|
||||||
@ -21,6 +23,31 @@ pub enum PayloadError {
|
|||||||
ParseError(IoError),
|
ParseError(IoError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PayloadError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
PayloadError::ParseError(ref e) => fmt::Display::fmt(e, f),
|
||||||
|
ref e => f.write_str(e.description()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for PayloadError {
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match *self {
|
||||||
|
PayloadError::Incomplete => "A payload reached EOF, but is not complete.",
|
||||||
|
PayloadError::ParseError(ref e) => e.description(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cause(&self) -> Option<&Error> {
|
||||||
|
match *self {
|
||||||
|
PayloadError::ParseError(ref error) => Some(error),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<IoError> for PayloadError {
|
impl From<IoError> for PayloadError {
|
||||||
fn from(err: IoError) -> PayloadError {
|
fn from(err: IoError) -> PayloadError {
|
||||||
PayloadError::ParseError(err)
|
PayloadError::ParseError(err)
|
||||||
|
Loading…
Reference in New Issue
Block a user