1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-02-24 21:03:21 +01:00

990 lines
33 KiB
Rust
Raw Normal View History

2021-04-01 15:26:13 +01:00
//! Multipart response payload support.
2020-09-10 14:46:35 +01:00
use std::{
cell::RefCell,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use actix_web::{
dev,
error::{ParseError, PayloadError},
http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue},
web::Bytes,
HttpRequest,
};
use futures_core::stream::Stream;
use mime::Mime;
2019-03-28 05:04:39 -07:00
use crate::{
error::MultipartError,
field::InnerField,
payload::{PayloadBuffer, PayloadRef},
safety::Safety,
Field,
};
2019-03-28 05:04:39 -07:00
const MAX_HEADERS: usize = 32;
2019-03-28 05:34:33 -07:00
2019-03-28 05:04:39 -07:00
/// The server-side implementation of `multipart/form-data` requests.
///
/// This will parse the incoming stream into `MultipartItem` instances via its `Stream`
/// implementation. `MultipartItem::Field` contains multipart field. `MultipartItem::Multipart` is
/// used for nested multipart streams.
2019-03-28 05:04:39 -07:00
pub struct Multipart {
safety: Safety,
inner: Option<InnerMultipart>,
error: Option<MultipartError>,
2019-03-28 05:04:39 -07:00
}
impl Multipart {
/// Creates multipart instance from parts.
pub fn new<S>(headers: &HeaderMap, stream: S) -> Self
2019-03-28 05:04:39 -07:00
where
S: Stream<Item = Result<Bytes, PayloadError>> + 'static,
2019-03-28 05:04:39 -07:00
{
match Self::find_ct_and_boundary(headers) {
Ok((ct, boundary)) => Self::from_ct_and_boundary(ct, boundary, stream),
Err(err) => Self::from_error(err),
2019-03-28 05:04:39 -07:00
}
}
/// Creates multipart instance from parts.
pub(crate) fn from_req(req: &HttpRequest, payload: &mut dev::Payload) -> Self {
match Self::find_ct_and_boundary(req.headers()) {
Ok((ct, boundary)) => Self::from_ct_and_boundary(ct, boundary, payload.take()),
Err(err) => Self::from_error(err),
}
}
/// Extract Content-Type and boundary info from headers.
pub(crate) fn find_ct_and_boundary(
headers: &HeaderMap,
) -> Result<(Mime, String), MultipartError> {
let content_type = headers
.get(&header::CONTENT_TYPE)
.ok_or(MultipartError::ContentTypeMissing)?
.to_str()
.ok()
.and_then(|content_type| content_type.parse::<Mime>().ok())
.ok_or(MultipartError::ContentTypeParse)?;
if content_type.type_() != mime::MULTIPART {
return Err(MultipartError::ContentTypeIncompatible);
}
let boundary = content_type
.get_param(mime::BOUNDARY)
.ok_or(MultipartError::BoundaryMissing)?
.as_str()
.to_owned();
Ok((content_type, boundary))
2019-03-28 05:04:39 -07:00
}
/// Constructs a new multipart reader from given Content-Type, boundary, and stream.
pub(crate) fn from_ct_and_boundary<S>(ct: Mime, boundary: String, stream: S) -> Multipart
where
S: Stream<Item = Result<Bytes, PayloadError>> + 'static,
{
Multipart {
safety: Safety::new(),
inner: Some(InnerMultipart {
payload: PayloadRef::new(PayloadBuffer::new(stream)),
content_type: ct,
boundary,
state: InnerState::FirstBoundary,
item: InnerMultipartItem::None,
}),
error: None,
}
}
/// Constructs a new multipart reader from given `MultipartError`.
pub(crate) fn from_error(err: MultipartError) -> Multipart {
Multipart {
error: Some(err),
safety: Safety::new(),
inner: None,
}
}
/// Return requests parsed Content-Type or raise the stored error.
pub(crate) fn content_type_or_bail(&mut self) -> Result<mime::Mime, MultipartError> {
if let Some(err) = self.error.take() {
return Err(err);
}
Ok(self
.inner
.as_ref()
// TODO: look into using enum instead of two options
.expect("multipart requests should have state")
.content_type
.clone())
}
2019-03-28 05:04:39 -07:00
}
impl Stream for Multipart {
2019-11-21 14:25:50 +06:00
type Item = Result<Field, MultipartError>;
2019-03-28 05:04:39 -07:00
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match this.inner.as_mut() {
Some(inner) => {
if let Some(mut buffer) = inner.payload.get_mut(&this.safety) {
// check safety and poll read payload to buffer.
buffer.poll_stream(cx)?;
} else if !this.safety.is_clean() {
// safety violation
return Poll::Ready(Some(Err(MultipartError::NotConsumed)));
} else {
return Poll::Pending;
}
inner.poll(&this.safety, cx)
}
None => Poll::Ready(Some(Err(this
.error
.take()
.expect("Multipart polled after finish")))),
2019-03-28 05:04:39 -07:00
}
}
}
#[derive(PartialEq, Debug)]
enum InnerState {
/// Stream EOF.
Eof,
/// Skip data until first boundary.
FirstBoundary,
/// Reading boundary.
Boundary,
/// Reading Headers.
Headers,
}
enum InnerMultipartItem {
None,
Field(Rc<RefCell<InnerField>>),
}
struct InnerMultipart {
/// Request's payload stream & buffer.
payload: PayloadRef,
/// Request's Content-Type.
///
/// Guaranteed to have "multipart" top-level media type, i.e., `multipart/*`.
content_type: Mime,
/// Field boundary.
boundary: String,
state: InnerState,
item: InnerMultipartItem,
}
2019-03-28 05:04:39 -07:00
impl InnerMultipart {
fn read_field_headers(
payload: &mut PayloadBuffer,
) -> Result<Option<HeaderMap>, MultipartError> {
2019-05-25 03:16:46 -07:00
match payload.read_until(b"\r\n\r\n")? {
None => {
if payload.eof {
Err(MultipartError::Incomplete)
} else {
Ok(None)
}
}
Some(bytes) => {
2019-03-28 05:04:39 -07:00
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
2019-03-28 05:04:39 -07:00
match httparse::parse_headers(&bytes, &mut hdrs) {
Ok(httparse::Status::Complete((_, hdrs))) => {
// convert headers
let mut headers = HeaderMap::with_capacity(hdrs.len());
2019-03-28 05:04:39 -07:00
for h in hdrs {
let name =
HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?;
2023-07-17 02:38:12 +01:00
let value =
HeaderValue::try_from(h.value).map_err(|_| ParseError::Header)?;
headers.append(name, value);
2019-03-28 05:04:39 -07:00
}
Ok(Some(headers))
2019-03-28 05:04:39 -07:00
}
Ok(httparse::Status::Partial) => Err(ParseError::Header.into()),
Err(err) => Err(ParseError::from(err).into()),
}
}
}
}
fn read_boundary(
payload: &mut PayloadBuffer,
boundary: &str,
) -> Result<Option<bool>, MultipartError> {
2019-03-28 05:04:39 -07:00
// TODO: need to read epilogue
match payload.readline_or_eof()? {
None => {
if payload.eof {
2019-04-21 15:41:01 -07:00
Ok(Some(true))
} else {
Ok(None)
}
}
Some(chunk) => {
if chunk.len() < boundary.len() + 4
|| &chunk[..2] != b"--"
2019-09-12 21:52:46 +06:00
|| &chunk[2..boundary.len() + 2] != boundary.as_bytes()
{
Err(MultipartError::BoundaryMissing)
} else if &chunk[boundary.len() + 2..] == b"\r\n" {
Ok(Some(false))
} else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
&& (chunk.len() == boundary.len() + 4
2019-09-12 21:52:46 +06:00
|| &chunk[boundary.len() + 4..] == b"\r\n")
{
Ok(Some(true))
2019-03-28 05:04:39 -07:00
} else {
Err(MultipartError::BoundaryMissing)
2019-03-28 05:04:39 -07:00
}
}
}
}
fn skip_until_boundary(
payload: &mut PayloadBuffer,
boundary: &str,
) -> Result<Option<bool>, MultipartError> {
2019-03-28 05:04:39 -07:00
let mut eof = false;
loop {
2019-05-25 03:16:46 -07:00
match payload.readline()? {
Some(chunk) => {
2019-03-28 05:04:39 -07:00
if chunk.is_empty() {
return Err(MultipartError::BoundaryMissing);
2019-03-28 05:04:39 -07:00
}
if chunk.len() < boundary.len() {
continue;
}
2023-07-17 02:38:12 +01:00
if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes() {
2019-03-28 05:04:39 -07:00
break;
} else {
if chunk.len() < boundary.len() + 2 {
continue;
}
let b: &[u8] = boundary.as_ref();
if &chunk[..boundary.len()] == b
&& &chunk[boundary.len()..boundary.len() + 2] == b"--"
{
eof = true;
break;
}
}
}
None => {
return if payload.eof {
Err(MultipartError::Incomplete)
} else {
Ok(None)
};
}
2019-03-28 05:04:39 -07:00
}
}
Ok(Some(eof))
2019-03-28 05:04:39 -07:00
}
2019-11-21 14:25:50 +06:00
fn poll(
&mut self,
safety: &Safety,
2023-08-29 01:11:11 +01:00
cx: &Context<'_>,
2019-11-21 14:25:50 +06:00
) -> Poll<Option<Result<Field, MultipartError>>> {
2019-03-28 05:04:39 -07:00
if self.state == InnerState::Eof {
2019-11-21 14:25:50 +06:00
Poll::Ready(None)
2019-03-28 05:04:39 -07:00
} else {
// release field
loop {
// Nested multipart streams of fields has to be consumed
// before switching to next
if safety.current() {
let stop = match self.item {
InnerMultipartItem::Field(ref mut field) => {
2019-11-21 14:25:50 +06:00
match field.borrow_mut().poll(safety) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Some(Ok(_))) => continue,
2023-07-17 02:38:12 +01:00
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
2019-11-21 14:25:50 +06:00
Poll::Ready(None) => true,
2019-03-28 05:04:39 -07:00
}
}
2019-04-13 10:11:07 -07:00
InnerMultipartItem::None => false,
2019-03-28 05:04:39 -07:00
};
if stop {
self.item = InnerMultipartItem::None;
}
if let InnerMultipartItem::None = self.item {
break;
}
}
}
let field_headers = if let Some(mut payload) = self.payload.get_mut(safety) {
2019-03-28 05:04:39 -07:00
match self.state {
// read until first boundary
InnerState::FirstBoundary => {
2023-07-17 02:38:12 +01:00
match InnerMultipart::skip_until_boundary(&mut payload, &self.boundary)? {
Some(eof) => {
2019-03-28 05:04:39 -07:00
if eof {
self.state = InnerState::Eof;
2019-11-21 14:25:50 +06:00
return Poll::Ready(None);
2019-03-28 05:04:39 -07:00
} else {
self.state = InnerState::Headers;
}
}
2019-11-21 14:25:50 +06:00
None => return Poll::Pending,
2019-03-28 05:04:39 -07:00
}
}
// read boundary
InnerState::Boundary => {
2022-09-25 20:54:17 +01:00
match InnerMultipart::read_boundary(&mut payload, &self.boundary)? {
2019-11-21 14:25:50 +06:00
None => return Poll::Pending,
Some(eof) => {
2019-03-28 05:04:39 -07:00
if eof {
self.state = InnerState::Eof;
2019-11-21 14:25:50 +06:00
return Poll::Ready(None);
2019-03-28 05:04:39 -07:00
} else {
self.state = InnerState::Headers;
}
}
}
}
2021-01-04 01:01:35 +00:00
_ => {}
2019-03-28 05:04:39 -07:00
}
// read field headers for next field
if self.state == InnerState::Headers {
if let Some(headers) = InnerMultipart::read_field_headers(&mut payload)? {
2019-03-28 05:04:39 -07:00
self.state = InnerState::Boundary;
headers
} else {
2019-11-21 14:25:50 +06:00
return Poll::Pending;
2019-03-28 05:04:39 -07:00
}
} else {
unreachable!()
}
} else {
log::debug!("NotReady: field is in flight");
2019-11-21 14:25:50 +06:00
return Poll::Pending;
2019-03-28 05:04:39 -07:00
};
let field_content_disposition = field_headers
.get(&header::CONTENT_DISPOSITION)
.and_then(|cd| ContentDisposition::from_raw(cd).ok())
.filter(|content_disposition| {
matches!(
content_disposition.disposition,
header::DispositionType::FormData,
)
});
let form_field_name = if self.content_type.subtype() == mime::FORM_DATA {
// According to RFC 7578 §4.2, which relates to "multipart/form-data" requests
// specifically, fields must have a Content-Disposition header, its disposition
// type must be set as "form-data", and it must have a name parameter.
let Some(cd) = &field_content_disposition else {
return Poll::Ready(Some(Err(MultipartError::ContentDispositionMissing)));
};
2019-03-28 05:04:39 -07:00
let Some(field_name) = cd.get_name() else {
return Poll::Ready(Some(Err(MultipartError::ContentDispositionNameMissing)));
};
Some(field_name.to_owned())
2019-03-28 05:04:39 -07:00
} else {
None
};
// TODO: check out other multipart/* RFCs for specific requirements
let field_content_type: Option<Mime> = field_headers
.get(&header::CONTENT_TYPE)
.and_then(|ct| ct.to_str().ok())
.and_then(|ct| ct.parse().ok());
self.state = InnerState::Boundary;
// nested multipart stream is not supported
if let Some(mime) = &field_content_type {
if mime.type_() == mime::MULTIPART {
return Poll::Ready(Some(Err(MultipartError::Nested)));
}
2019-03-28 05:04:39 -07:00
}
let field_inner =
InnerField::new_in_rc(self.payload.clone(), self.boundary.clone(), &field_headers)?;
self.item = InnerMultipartItem::Field(Rc::clone(&field_inner));
Poll::Ready(Some(Ok(Field::new(
field_content_type,
field_content_disposition,
form_field_name,
field_headers,
safety.clone(cx),
field_inner,
))))
2019-03-28 05:04:39 -07:00
}
}
}
impl Drop for InnerMultipart {
fn drop(&mut self) {
// InnerMultipartItem::Field has to be dropped first because of Safety.
self.item = InnerMultipartItem::None;
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
2019-11-21 14:25:50 +06:00
use actix_http::h1;
use actix_web::{
http::header::{DispositionParam, DispositionType},
rt,
test::TestRequest,
web::{BufMut as _, BytesMut},
FromRequest,
};
use assert_matches::assert_matches;
use futures_util::{future::lazy, StreamExt as _};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
2019-03-28 05:04:39 -07:00
use super::*;
const BOUNDARY: &str = "abbc761f78ff4d7cb7573b5a23f96ef0";
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_boundary() {
2019-03-28 05:04:39 -07:00
let headers = HeaderMap::new();
match Multipart::find_ct_and_boundary(&headers) {
Err(MultipartError::ContentTypeMissing) => {}
2019-03-28 05:04:39 -07:00
_ => unreachable!("should not happen"),
}
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("test"),
);
match Multipart::find_ct_and_boundary(&headers) {
Err(MultipartError::ContentTypeParse) => {}
2019-03-28 05:04:39 -07:00
_ => unreachable!("should not happen"),
}
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("multipart/mixed"),
);
match Multipart::find_ct_and_boundary(&headers) {
Err(MultipartError::BoundaryMissing) => {}
2019-03-28 05:04:39 -07:00
_ => unreachable!("should not happen"),
}
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"5c02368e880e436dab70ed54e1c58209\"",
),
);
assert_eq!(
Multipart::find_ct_and_boundary(&headers).unwrap().1,
"5c02368e880e436dab70ed54e1c58209",
2019-03-28 05:04:39 -07:00
);
}
fn create_stream() -> (
mpsc::UnboundedSender<Result<Bytes, PayloadError>>,
2019-11-21 14:25:50 +06:00
impl Stream<Item = Result<Bytes, PayloadError>>,
2019-03-28 05:04:39 -07:00
) {
let (tx, rx) = mpsc::unbounded_channel();
2019-03-28 05:04:39 -07:00
(
tx,
UnboundedReceiverStream::new(rx).map(|res| res.map_err(|_| panic!())),
)
2019-03-28 05:04:39 -07:00
}
// Stream that returns from a Bytes, one char at a time and Pending every other poll()
struct SlowStream {
bytes: Bytes,
pos: usize,
ready: bool,
}
impl SlowStream {
fn new(bytes: Bytes) -> SlowStream {
SlowStream {
bytes,
pos: 0,
ready: false,
}
}
}
impl Stream for SlowStream {
type Item = Result<Bytes, PayloadError>;
2021-02-11 23:03:17 +00:00
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if !this.ready {
this.ready = true;
cx.waker().wake_by_ref();
return Poll::Pending;
}
if this.pos == this.bytes.len() {
return Poll::Ready(None);
}
let res = Poll::Ready(Some(Ok(this.bytes.slice(this.pos..(this.pos + 1)))));
this.pos += 1;
this.ready = false;
res
}
}
2019-03-28 05:04:39 -07:00
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
let (body, headers) = crate::test::create_form_data_payload_and_headers_with_boundary(
BOUNDARY,
"file",
Some("fn.txt".to_owned()),
Some(mime::TEXT_PLAIN_UTF_8),
Bytes::from_static(b"data"),
);
let mut buf = BytesMut::with_capacity(body.len() + 14);
// add junk before form to test pre-boundary data rejection
buf.put("testasdadsad\r\n".as_bytes());
buf.put(body);
(buf.freeze(), headers)
}
// TODO: use test utility when multi-file support is introduced
fn create_double_request_with_header() -> (Bytes, HeaderMap) {
let bytes = Bytes::from(
"testasdadsad\r\n\
2019-09-12 21:52:46 +06:00
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
2019-09-12 21:52:46 +06:00
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
(bytes, headers)
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_multipart_no_end_crlf() {
let (sender, payload) = create_stream();
let (mut bytes, headers) = create_double_request_with_header();
2019-12-05 23:35:43 +06:00
let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf
2019-11-26 11:25:50 +06:00
sender.send(Ok(bytes_stripped)).unwrap();
drop(sender); // eof
2019-11-26 11:25:50 +06:00
let mut multipart = Multipart::new(&headers, payload);
2019-11-26 11:25:50 +06:00
match multipart.next().await.unwrap() {
2021-01-04 01:01:35 +00:00
Ok(_) => {}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-11-26 11:25:50 +06:00
match multipart.next().await.unwrap() {
2021-01-04 01:01:35 +00:00
Ok(_) => {}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-11-26 11:25:50 +06:00
match multipart.next().await {
2021-01-04 01:01:35 +00:00
None => {}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_multipart() {
let (sender, payload) = create_stream();
let (bytes, headers) = create_double_request_with_header();
2019-03-28 05:04:39 -07:00
2019-11-26 11:25:50 +06:00
sender.send(Ok(bytes)).unwrap();
2019-03-28 05:04:39 -07:00
2019-11-26 11:25:50 +06:00
let mut multipart = Multipart::new(&headers, payload);
match multipart.next().await {
Some(Ok(mut field)) => {
let cd = field.content_disposition().unwrap();
2019-11-26 11:25:50 +06:00
assert_eq!(cd.disposition, DispositionType::FormData);
assert_eq!(cd.parameters[0], DispositionParam::Name("file".into()));
2019-04-21 16:14:09 -07:00
assert_eq!(field.content_type().unwrap().type_(), mime::TEXT);
assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN);
2019-04-21 16:14:09 -07:00
2019-11-26 11:25:50 +06:00
match field.next().await.unwrap() {
Ok(chunk) => assert_eq!(chunk, "test"),
_ => unreachable!(),
}
match field.next().await {
2021-01-04 01:01:35 +00:00
None => {}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
2019-04-21 16:14:09 -07:00
}
}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-04-21 16:14:09 -07:00
2019-11-26 11:25:50 +06:00
match multipart.next().await.unwrap() {
Ok(mut field) => {
assert_eq!(field.content_type().unwrap().type_(), mime::TEXT);
assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN);
2019-04-21 16:14:09 -07:00
2019-11-26 11:25:50 +06:00
match field.next().await {
Some(Ok(chunk)) => assert_eq!(chunk, "data"),
_ => unreachable!(),
}
match field.next().await {
2021-01-04 01:01:35 +00:00
None => {}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
2019-04-21 16:14:09 -07:00
}
}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-04-21 16:14:09 -07:00
2019-11-26 11:25:50 +06:00
match multipart.next().await {
2021-01-04 01:01:35 +00:00
None => {}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-04-21 16:14:09 -07:00
}
// Loops, collecting all bytes until end-of-field
async fn get_whole_field(field: &mut Field) -> BytesMut {
let mut b = BytesMut::new();
loop {
match field.next().await {
Some(Ok(chunk)) => b.extend_from_slice(&chunk),
None => return b,
_ => unreachable!(),
}
}
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_stream() {
let (bytes, headers) = create_double_request_with_header();
let payload = SlowStream::new(bytes);
2019-04-21 16:14:09 -07:00
2019-11-26 11:25:50 +06:00
let mut multipart = Multipart::new(&headers, payload);
match multipart.next().await.unwrap() {
Ok(mut field) => {
let cd = field.content_disposition().unwrap();
2019-11-26 11:25:50 +06:00
assert_eq!(cd.disposition, DispositionType::FormData);
assert_eq!(cd.parameters[0], DispositionParam::Name("file".into()));
2019-03-28 05:04:39 -07:00
assert_eq!(field.content_type().unwrap().type_(), mime::TEXT);
assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN);
2019-04-13 10:11:07 -07:00
assert_eq!(get_whole_field(&mut field).await, "test");
2019-03-28 05:04:39 -07:00
}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-03-28 05:04:39 -07:00
2019-11-26 11:25:50 +06:00
match multipart.next().await {
Some(Ok(mut field)) => {
assert_eq!(field.content_type().unwrap().type_(), mime::TEXT);
assert_eq!(field.content_type().unwrap().subtype(), mime::PLAIN);
2019-04-13 10:11:07 -07:00
assert_eq!(get_whole_field(&mut field).await, "data");
2019-03-28 05:04:39 -07:00
}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-03-28 05:04:39 -07:00
2019-11-26 11:25:50 +06:00
match multipart.next().await {
2021-01-04 01:01:35 +00:00
None => {}
2019-11-26 11:25:50 +06:00
_ => unreachable!(),
}
2019-03-28 05:04:39 -07:00
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_basic() {
let (_, payload) = h1::Payload::create(false);
2019-11-26 11:25:50 +06:00
let mut payload = PayloadBuffer::new(payload);
2019-11-26 11:25:50 +06:00
assert_eq!(payload.buf.len(), 0);
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(None, payload.read_max(1).unwrap());
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_eof() {
let (mut sender, payload) = h1::Payload::create(false);
2019-11-26 11:25:50 +06:00
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(4).unwrap());
sender.feed_data(Bytes::from("data"));
sender.feed_eof();
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap());
assert_eq!(payload.buf.len(), 0);
assert!(payload.read_max(1).is_err());
assert!(payload.eof);
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_err() {
let (mut sender, payload) = h1::Payload::create(false);
2019-11-26 11:25:50 +06:00
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(1).unwrap());
sender.set_error(PayloadError::Incomplete(None));
lazy(|cx| payload.poll_stream(cx)).await.err().unwrap();
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_readmax() {
let (mut sender, payload) = h1::Payload::create(false);
2019-11-26 11:25:50 +06:00
let mut payload = PayloadBuffer::new(payload);
2019-11-26 11:25:50 +06:00
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(payload.buf.len(), 10);
2019-11-26 11:25:50 +06:00
assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap());
assert_eq!(payload.buf.len(), 5);
2019-11-26 11:25:50 +06:00
assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap());
assert_eq!(payload.buf.len(), 0);
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_readexactly() {
let (mut sender, payload) = h1::Payload::create(false);
2019-11-26 11:25:50 +06:00
let mut payload = PayloadBuffer::new(payload);
2019-11-26 11:25:50 +06:00
assert_eq!(None, payload.read_exact(2));
2019-11-26 11:25:50 +06:00
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
2019-11-26 11:25:50 +06:00
assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2));
assert_eq!(payload.buf.len(), 8);
2019-11-26 11:25:50 +06:00
assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4));
assert_eq!(payload.buf.len(), 4);
}
2019-11-26 11:25:50 +06:00
#[actix_rt::test]
async fn test_readuntil() {
let (mut sender, payload) = h1::Payload::create(false);
2019-11-26 11:25:50 +06:00
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_until(b"ne").unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(
Some(Bytes::from("line")),
payload.read_until(b"ne").unwrap()
);
assert_eq!(payload.buf.len(), 6);
assert_eq!(
Some(Bytes::from("1line2")),
payload.read_until(b"2").unwrap()
);
assert_eq!(payload.buf.len(), 0);
}
#[actix_rt::test]
async fn test_multipart_from_error() {
let err = MultipartError::ContentTypeMissing;
let mut multipart = Multipart::from_error(err);
assert!(multipart.next().await.unwrap().is_err())
}
#[actix_rt::test]
async fn test_multipart_from_boundary() {
let (_, payload) = create_stream();
let (_, headers) = create_simple_request_with_header();
let (ct, boundary) = Multipart::find_ct_and_boundary(&headers).unwrap();
let _ = Multipart::from_ct_and_boundary(ct, boundary, payload);
}
#[actix_rt::test]
async fn test_multipart_payload_consumption() {
// with sample payload and HttpRequest with no headers
let (_, inner_payload) = h1::Payload::create(false);
let mut payload = actix_web::dev::Payload::from(inner_payload);
let req = TestRequest::default().to_http_request();
// multipart should generate an error
let mut mp = Multipart::from_request(&req, &mut payload).await.unwrap();
assert!(mp.next().await.unwrap().is_err());
// and should not consume the payload
match payload {
actix_web::dev::Payload::H1 { .. } => {} //expected
_ => unreachable!(),
}
}
#[actix_rt::test]
async fn no_content_disposition_form_data() {
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\n\
Content-Length: 4\r\n\
\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n",
);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/form-data; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let payload = SlowStream::new(bytes);
let mut multipart = Multipart::new(&headers, payload);
let res = multipart.next().await.unwrap();
assert_matches!(
res.expect_err(
"according to RFC 7578, form-data fields require a content-disposition header"
),
MultipartError::ContentDispositionMissing
);
}
#[actix_rt::test]
async fn no_content_disposition_non_form_data() {
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\n\
Content-Length: 4\r\n\
\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n",
);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let payload = SlowStream::new(bytes);
let mut multipart = Multipart::new(&headers, payload);
let res = multipart.next().await.unwrap();
res.unwrap();
}
#[actix_rt::test]
async fn no_name_in_form_data_content_disposition() {
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\
Content-Length: 4\r\n\
\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n",
);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/form-data; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let payload = SlowStream::new(bytes);
let mut multipart = Multipart::new(&headers, payload);
let res = multipart.next().await.unwrap();
assert_matches!(
res.expect_err("according to RFC 7578, form-data fields require a name attribute"),
MultipartError::ContentDispositionNameMissing
);
}
#[actix_rt::test]
async fn test_drop_multipart_dont_hang() {
let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
sender.send(Ok(bytes)).unwrap();
drop(sender); // eof
let mut multipart = Multipart::new(&headers, payload);
let mut field = multipart.next().await.unwrap().unwrap();
drop(multipart);
// should fail immediately
match field.next().await {
Some(Err(MultipartError::NotConsumed)) => {}
_ => panic!(),
};
}
#[actix_rt::test]
async fn test_drop_field_awaken_multipart() {
let (sender, payload) = create_stream();
let (bytes, headers) = create_double_request_with_header();
sender.send(Ok(bytes)).unwrap();
drop(sender); // eof
let mut multipart = Multipart::new(&headers, payload);
let mut field = multipart.next().await.unwrap().unwrap();
let task = rt::spawn(async move {
rt::time::sleep(Duration::from_millis(500)).await;
assert_eq!(field.next().await.unwrap().unwrap(), "test");
drop(field);
});
// dropping field should awaken current task
let _ = multipart.next().await.unwrap().unwrap();
task.await.unwrap();
}
2019-03-28 05:04:39 -07:00
}