2021-04-01 15:26:13 +01:00
|
|
|
//! Multipart response payload support.
|
2020-09-10 14:46:35 +01:00
|
|
|
|
2021-11-17 17:43:24 +00:00
|
|
|
use std::{
|
2024-07-04 00:37:25 +01:00
|
|
|
cell::RefCell,
|
2023-07-02 01:09:15 +01:00
|
|
|
cmp, fmt,
|
2021-11-17 17:43:24 +00:00
|
|
|
pin::Pin,
|
|
|
|
rc::Rc,
|
|
|
|
task::{Context, Poll},
|
|
|
|
};
|
|
|
|
|
|
|
|
use actix_web::{
|
2024-07-01 03:55:08 +01:00
|
|
|
dev,
|
2021-11-17 17:43:24 +00:00
|
|
|
error::{ParseError, PayloadError},
|
|
|
|
http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue},
|
2024-07-04 00:37:25 +01:00
|
|
|
web::Bytes,
|
2024-07-01 03:55:08 +01:00
|
|
|
HttpRequest,
|
2021-11-17 17:43:24 +00:00
|
|
|
};
|
2024-07-04 00:37:25 +01:00
|
|
|
use futures_core::stream::Stream;
|
2024-07-01 03:55:08 +01:00
|
|
|
use mime::Mime;
|
2019-03-28 05:04:39 -07:00
|
|
|
|
2024-07-04 00:37:25 +01:00
|
|
|
use crate::{
|
|
|
|
error::MultipartError,
|
|
|
|
payload::{PayloadBuffer, PayloadRef},
|
|
|
|
safety::Safety,
|
|
|
|
};
|
2019-03-28 05:04:39 -07:00
|
|
|
|
2019-04-03 12:28:58 -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.
|
|
|
|
///
|
2024-07-01 03:55:08 +01:00
|
|
|
/// 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,
|
2021-11-25 08:10:53 +08:00
|
|
|
inner: Option<InnerMultipart>,
|
2024-07-01 03:55:08 +01:00
|
|
|
error: Option<MultipartError>,
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Multipart {
|
2024-07-01 03:55:08 +01:00
|
|
|
/// Creates multipart instance from parts.
|
|
|
|
pub fn new<S>(headers: &HeaderMap, stream: S) -> Self
|
2019-03-28 05:04:39 -07:00
|
|
|
where
|
2021-11-25 04:53:11 +08:00
|
|
|
S: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
2019-03-28 05:04:39 -07:00
|
|
|
{
|
2024-07-01 03:55:08 +01: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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-01 03:55:08 +01: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
|
2021-11-25 04:53:11 +08:00
|
|
|
.get(&header::CONTENT_TYPE)
|
2024-07-01 03:55:08 +01:00
|
|
|
.ok_or(MultipartError::ContentTypeMissing)?
|
2021-11-25 04:53:11 +08:00
|
|
|
.to_str()
|
|
|
|
.ok()
|
2024-07-01 03:55:08 +01:00
|
|
|
.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
|
2021-11-25 04:53:11 +08:00
|
|
|
.get_param(mime::BOUNDARY)
|
2024-07-01 03:55:08 +01:00
|
|
|
.ok_or(MultipartError::BoundaryMissing)?
|
|
|
|
.as_str()
|
|
|
|
.to_owned();
|
|
|
|
|
|
|
|
Ok((content_type, boundary))
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2020-09-25 15:50:37 +02:00
|
|
|
|
2024-07-01 03:55:08 +01: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
|
2020-09-25 15:50:37 +02:00
|
|
|
where
|
2021-11-25 04:53:11 +08:00
|
|
|
S: Stream<Item = Result<Bytes, PayloadError>> + 'static,
|
2020-09-25 15:50:37 +02:00
|
|
|
{
|
|
|
|
Multipart {
|
|
|
|
safety: Safety::new(),
|
2021-11-25 08:10:53 +08:00
|
|
|
inner: Some(InnerMultipart {
|
2021-11-25 04:53:11 +08:00
|
|
|
payload: PayloadRef::new(PayloadBuffer::new(stream)),
|
2024-07-01 03:55:08 +01:00
|
|
|
content_type: ct,
|
|
|
|
boundary,
|
2020-09-25 15:50:37 +02:00
|
|
|
state: InnerState::FirstBoundary,
|
|
|
|
item: InnerMultipartItem::None,
|
2021-11-25 08:10:53 +08:00
|
|
|
}),
|
2024-07-01 03:55:08 +01:00
|
|
|
error: None,
|
2020-09-25 15:50:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
/// Constructs a new multipart reader from given `MultipartError`.
|
2020-09-25 15:50:37 +02:00
|
|
|
pub(crate) fn from_error(err: MultipartError) -> Multipart {
|
|
|
|
Multipart {
|
|
|
|
error: Some(err),
|
|
|
|
safety: Safety::new(),
|
|
|
|
inner: None,
|
|
|
|
}
|
|
|
|
}
|
2024-07-01 03:55:08 +01:00
|
|
|
|
|
|
|
/// 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
|
|
|
|
2021-11-25 08:10:53 +08: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)
|
2019-04-03 12:28:58 -07:00
|
|
|
}
|
2021-11-25 08:10:53 +08:00
|
|
|
None => Poll::Ready(Some(Err(this
|
|
|
|
.error
|
|
|
|
.take()
|
|
|
|
.expect("Multipart polled after finish")))),
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-01 03:55:08 +01: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 {
|
2024-07-01 03:55:08 +01:00
|
|
|
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")? {
|
2019-04-03 12:28:58 -07:00
|
|
|
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];
|
2024-07-01 03:55:08 +01:00
|
|
|
|
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());
|
2021-11-25 08:10:53 +08:00
|
|
|
|
2019-03-28 05:04:39 -07:00
|
|
|
for h in hdrs {
|
2021-11-25 08:10:53 +08:00
|
|
|
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)?;
|
2021-11-25 08:10:53 +08:00
|
|
|
headers.append(name, value);
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2021-11-25 08:10:53 +08:00
|
|
|
|
2019-04-03 12:28:58 -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,
|
2019-04-03 12:28:58 -07:00
|
|
|
) -> Result<Option<bool>, MultipartError> {
|
2019-03-28 05:04:39 -07:00
|
|
|
// TODO: need to read epilogue
|
2019-09-09 17:58:00 +10:00
|
|
|
match payload.readline_or_eof()? {
|
2019-04-03 12:28:58 -07:00
|
|
|
None => {
|
|
|
|
if payload.eof {
|
2019-04-21 15:41:01 -07:00
|
|
|
Ok(Some(true))
|
2019-04-03 12:28:58 -07:00
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Some(chunk) => {
|
2019-09-09 17:58:00 +10:00
|
|
|
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()
|
|
|
|
{
|
2024-07-01 03:55:08 +01:00
|
|
|
Err(MultipartError::BoundaryMissing)
|
2019-09-09 17:58:00 +10:00
|
|
|
} else if &chunk[boundary.len() + 2..] == b"\r\n" {
|
2019-04-03 12:28:58 -07:00
|
|
|
Ok(Some(false))
|
2019-09-09 17:58:00 +10:00
|
|
|
} 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")
|
|
|
|
{
|
2019-04-03 12:28:58 -07:00
|
|
|
Ok(Some(true))
|
2019-03-28 05:04:39 -07:00
|
|
|
} else {
|
2024-07-01 03:55:08 +01:00
|
|
|
Err(MultipartError::BoundaryMissing)
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn skip_until_boundary(
|
|
|
|
payload: &mut PayloadBuffer,
|
|
|
|
boundary: &str,
|
2019-04-03 12:28:58 -07:00
|
|
|
) -> 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()? {
|
2019-04-03 12:28:58 -07:00
|
|
|
Some(chunk) => {
|
2019-03-28 05:04:39 -07:00
|
|
|
if chunk.is_empty() {
|
2024-07-01 03:55:08 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-04-03 12:28:58 -07:00
|
|
|
None => {
|
|
|
|
return if payload.eof {
|
|
|
|
Err(MultipartError::Incomplete)
|
|
|
|
} else {
|
|
|
|
Ok(None)
|
|
|
|
};
|
|
|
|
}
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
2019-04-03 12:28:58 -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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
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)? {
|
2019-04-03 12:28:58 -07:00
|
|
|
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,
|
2019-04-03 12:28:58 -07:00
|
|
|
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 {
|
2024-07-01 03:55:08 +01:00
|
|
|
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
|
|
|
};
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
let field_content_disposition = field_headers
|
2021-11-17 17:43:24 +00:00
|
|
|
.get(&header::CONTENT_DISPOSITION)
|
|
|
|
.and_then(|cd| ContentDisposition::from_raw(cd).ok())
|
|
|
|
.filter(|content_disposition| {
|
2024-07-01 03:55:08 +01:00
|
|
|
matches!(
|
|
|
|
content_disposition.disposition,
|
|
|
|
header::DispositionType::FormData,
|
|
|
|
)
|
|
|
|
});
|
2021-11-17 17:43:24 +00:00
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
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.
|
2021-11-17 17:43:24 +00:00
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
let Some(cd) = &field_content_disposition else {
|
|
|
|
return Poll::Ready(Some(Err(MultipartError::ContentDispositionMissing)));
|
|
|
|
};
|
2019-03-28 05:04:39 -07:00
|
|
|
|
2024-07-01 03:55:08 +01: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 {
|
2024-07-01 03:55:08 +01:00
|
|
|
None
|
2021-11-17 17:43:24 +00:00
|
|
|
};
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
// TODO: check out other multipart/* RFCs for specific requirements
|
|
|
|
|
|
|
|
let field_content_type: Option<Mime> = field_headers
|
2021-11-17 17:43:24 +00:00
|
|
|
.get(&header::CONTENT_TYPE)
|
|
|
|
.and_then(|ct| ct.to_str().ok())
|
2022-09-23 18:06:40 +01:00
|
|
|
.and_then(|ct| ct.parse().ok());
|
2021-11-17 17:43:24 +00:00
|
|
|
|
|
|
|
self.state = InnerState::Boundary;
|
|
|
|
|
|
|
|
// nested multipart stream is not supported
|
2024-07-01 03:55:08 +01:00
|
|
|
if let Some(mime) = &field_content_type {
|
2022-09-23 18:06:40 +01:00
|
|
|
if mime.type_() == mime::MULTIPART {
|
|
|
|
return Poll::Ready(Some(Err(MultipartError::Nested)));
|
|
|
|
}
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2021-11-17 17:43:24 +00:00
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
let field_inner =
|
|
|
|
InnerField::new_in_rc(self.payload.clone(), self.boundary.clone(), &field_headers)?;
|
2021-11-17 17:43:24 +00:00
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
self.item = InnerMultipartItem::Field(Rc::clone(&field_inner));
|
2021-11-17 17:43:24 +00:00
|
|
|
|
|
|
|
Poll::Ready(Some(Ok(Field::new(
|
2024-07-01 03:55:08 +01:00
|
|
|
field_content_type,
|
|
|
|
field_content_disposition,
|
|
|
|
form_field_name,
|
|
|
|
field_headers,
|
2021-11-17 17:43:24 +00:00
|
|
|
safety.clone(cx),
|
2024-07-01 03:55:08 +01:00
|
|
|
field_inner,
|
2021-11-17 17:43:24 +00:00
|
|
|
))))
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
/// A single field in a multipart stream.
|
2019-04-03 12:28:58 -07:00
|
|
|
pub struct Field {
|
2024-07-01 03:55:08 +01:00
|
|
|
/// Field's Content-Type.
|
|
|
|
content_type: Option<Mime>,
|
|
|
|
|
|
|
|
/// Field's Content-Disposition.
|
|
|
|
content_disposition: Option<ContentDisposition>,
|
|
|
|
|
|
|
|
/// Form field name.
|
|
|
|
///
|
|
|
|
/// A non-optional storage for form field names to avoid unwraps in `form` module. Will be an
|
|
|
|
/// empty string in non-form contexts.
|
|
|
|
///
|
|
|
|
// INVARIANT: always non-empty when request content-type is multipart/form-data.
|
|
|
|
pub(crate) form_field_name: String,
|
|
|
|
|
|
|
|
/// Field's header map.
|
2019-03-28 05:04:39 -07:00
|
|
|
headers: HeaderMap,
|
2024-07-01 03:55:08 +01:00
|
|
|
|
2019-03-28 05:04:39 -07:00
|
|
|
safety: Safety,
|
2024-07-01 03:55:08 +01:00
|
|
|
inner: Rc<RefCell<InnerField>>,
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
|
2019-04-03 12:28:58 -07:00
|
|
|
impl Field {
|
2019-03-28 05:04:39 -07:00
|
|
|
fn new(
|
2024-07-01 03:55:08 +01:00
|
|
|
content_type: Option<Mime>,
|
|
|
|
content_disposition: Option<ContentDisposition>,
|
|
|
|
form_field_name: Option<String>,
|
2019-03-28 05:04:39 -07:00
|
|
|
headers: HeaderMap,
|
2024-07-01 03:55:08 +01:00
|
|
|
safety: Safety,
|
2019-03-28 05:04:39 -07:00
|
|
|
inner: Rc<RefCell<InnerField>>,
|
|
|
|
) -> Self {
|
2019-04-03 12:28:58 -07:00
|
|
|
Field {
|
2024-07-01 03:55:08 +01:00
|
|
|
content_type,
|
|
|
|
content_disposition,
|
|
|
|
form_field_name: form_field_name.unwrap_or_default(),
|
2019-03-28 05:04:39 -07:00
|
|
|
headers,
|
|
|
|
inner,
|
|
|
|
safety,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-17 17:43:24 +00:00
|
|
|
/// Returns a reference to the field's header map.
|
2019-03-28 05:04:39 -07:00
|
|
|
pub fn headers(&self) -> &HeaderMap {
|
|
|
|
&self.headers
|
|
|
|
}
|
|
|
|
|
2022-09-23 18:06:40 +01:00
|
|
|
/// Returns a reference to the field's content (mime) type, if it is supplied by the client.
|
|
|
|
///
|
|
|
|
/// According to [RFC 7578](https://www.rfc-editor.org/rfc/rfc7578#section-4.4), if it is not
|
|
|
|
/// present, it should default to "text/plain". Note it is the responsibility of the client to
|
|
|
|
/// provide the appropriate content type, there is no attempt to validate this by the server.
|
2024-07-01 03:55:08 +01:00
|
|
|
pub fn content_type(&self) -> Option<&Mime> {
|
|
|
|
self.content_type.as_ref()
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
/// Returns this field's parsed Content-Disposition header, if set.
|
|
|
|
///
|
|
|
|
/// # Validation
|
2021-11-17 17:43:24 +00:00
|
|
|
///
|
2024-07-01 03:55:08 +01:00
|
|
|
/// Per [RFC 7578 §4.2], the parts of a multipart/form-data payload MUST contain a
|
|
|
|
/// Content-Disposition header field where the disposition type is `form-data` and MUST also
|
|
|
|
/// contain an additional parameter of `name` with its value being the original field name from
|
|
|
|
/// the form. This requirement is enforced during extraction for multipart/form-data requests,
|
|
|
|
/// but not other kinds of multipart requests (such as multipart/related).
|
2021-11-17 17:43:24 +00:00
|
|
|
///
|
2024-07-01 03:55:08 +01:00
|
|
|
/// As such, it is safe to `.unwrap()` calls `.content_disposition()` if you've verified.
|
|
|
|
///
|
|
|
|
/// The [`name()`](Self::name) method is also provided as a convenience for obtaining the
|
|
|
|
/// aforementioned name parameter.
|
2021-11-17 17:43:24 +00:00
|
|
|
///
|
|
|
|
/// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
|
2024-07-01 03:55:08 +01:00
|
|
|
pub fn content_disposition(&self) -> Option<&ContentDisposition> {
|
|
|
|
self.content_disposition.as_ref()
|
2021-11-17 17:43:24 +00:00
|
|
|
}
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
/// Returns the field's name, if set.
|
2021-11-17 17:43:24 +00:00
|
|
|
///
|
2024-07-01 03:55:08 +01:00
|
|
|
/// See [`content_disposition()`](Self::content_disposition) regarding guarantees on presence of
|
|
|
|
/// the "name" field.
|
|
|
|
pub fn name(&self) -> Option<&str> {
|
|
|
|
self.content_disposition()?.get_name()
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-03 12:28:58 -07:00
|
|
|
impl Stream for Field {
|
2019-11-21 14:25:50 +06:00
|
|
|
type Item = Result<Bytes, MultipartError>;
|
2019-03-28 05:04:39 -07:00
|
|
|
|
2021-02-11 23:03:17 +00:00
|
|
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
2021-11-25 08:10:53 +08:00
|
|
|
let this = self.get_mut();
|
|
|
|
let mut inner = this.inner.borrow_mut();
|
2024-07-01 03:55:08 +01:00
|
|
|
|
2024-06-10 21:51:53 +01:00
|
|
|
if let Some(mut buffer) = inner
|
|
|
|
.payload
|
|
|
|
.as_ref()
|
|
|
|
.expect("Field should not be polled after completion")
|
|
|
|
.get_mut(&this.safety)
|
|
|
|
{
|
2021-11-25 08:10:53 +08:00
|
|
|
// 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)));
|
2019-03-28 05:04:39 -07:00
|
|
|
} else {
|
2021-11-25 08:10:53 +08:00
|
|
|
return Poll::Pending;
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2021-11-25 08:10:53 +08:00
|
|
|
|
|
|
|
inner.poll(&this.safety)
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-03 12:28:58 -07:00
|
|
|
impl fmt::Debug for Field {
|
2020-09-10 14:46:35 +01:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-07-01 03:55:08 +01:00
|
|
|
if let Some(ct) = &self.content_type {
|
2022-09-23 18:06:40 +01:00
|
|
|
writeln!(f, "\nField: {}", ct)?;
|
|
|
|
} else {
|
|
|
|
writeln!(f, "\nField:")?;
|
|
|
|
}
|
2019-03-28 05:04:39 -07:00
|
|
|
writeln!(f, " boundary: {}", self.inner.borrow().boundary)?;
|
|
|
|
writeln!(f, " headers:")?;
|
|
|
|
for (key, val) in self.headers.iter() {
|
|
|
|
writeln!(f, " {:?}: {:?}", key, val)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct InnerField {
|
2024-06-10 21:51:53 +01:00
|
|
|
/// Payload is initialized as Some and is `take`n when the field stream finishes.
|
2019-03-28 05:04:39 -07:00
|
|
|
payload: Option<PayloadRef>,
|
|
|
|
boundary: String,
|
|
|
|
eof: bool,
|
|
|
|
length: Option<u64>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl InnerField {
|
2021-11-17 17:43:24 +00:00
|
|
|
fn new_in_rc(
|
|
|
|
payload: PayloadRef,
|
|
|
|
boundary: String,
|
|
|
|
headers: &HeaderMap,
|
|
|
|
) -> Result<Rc<RefCell<InnerField>>, PayloadError> {
|
|
|
|
Self::new(payload, boundary, headers).map(|this| Rc::new(RefCell::new(this)))
|
|
|
|
}
|
|
|
|
|
2019-03-28 05:04:39 -07:00
|
|
|
fn new(
|
|
|
|
payload: PayloadRef,
|
|
|
|
boundary: String,
|
|
|
|
headers: &HeaderMap,
|
|
|
|
) -> Result<InnerField, PayloadError> {
|
2019-04-06 15:02:02 -07:00
|
|
|
let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) {
|
2021-11-17 17:43:24 +00:00
|
|
|
match len.to_str().ok().and_then(|len| len.parse::<u64>().ok()) {
|
|
|
|
Some(len) => Some(len),
|
|
|
|
None => return Err(PayloadError::Incomplete(None)),
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(InnerField {
|
|
|
|
boundary,
|
|
|
|
payload: Some(payload),
|
|
|
|
eof: false,
|
|
|
|
length: len,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads body part content chunk of the specified size.
|
|
|
|
/// The body part must has `Content-Length` header with proper value.
|
|
|
|
fn read_len(
|
|
|
|
payload: &mut PayloadBuffer,
|
|
|
|
size: &mut u64,
|
2019-11-21 14:25:50 +06:00
|
|
|
) -> Poll<Option<Result<Bytes, MultipartError>>> {
|
2019-03-28 05:04:39 -07:00
|
|
|
if *size == 0 {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(None)
|
2019-03-28 05:04:39 -07:00
|
|
|
} else {
|
2019-05-25 03:16:46 -07:00
|
|
|
match payload.read_max(*size)? {
|
2019-04-03 12:28:58 -07:00
|
|
|
Some(mut chunk) => {
|
2019-03-28 05:04:39 -07:00
|
|
|
let len = cmp::min(chunk.len() as u64, *size);
|
|
|
|
*size -= len;
|
|
|
|
let ch = chunk.split_to(len as usize);
|
|
|
|
if !chunk.is_empty() {
|
|
|
|
payload.unprocessed(chunk);
|
|
|
|
}
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(Some(Ok(ch)))
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2019-04-03 12:28:58 -07:00
|
|
|
None => {
|
|
|
|
if payload.eof && (*size != 0) {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(Some(Err(MultipartError::Incomplete)))
|
2019-04-03 12:28:58 -07:00
|
|
|
} else {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Pending
|
2019-04-03 12:28:58 -07:00
|
|
|
}
|
|
|
|
}
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reads content chunk of body part with unknown length.
|
2024-07-01 03:55:08 +01:00
|
|
|
///
|
2019-03-28 05:04:39 -07:00
|
|
|
/// The `Content-Length` header for body part is not necessary.
|
|
|
|
fn read_stream(
|
|
|
|
payload: &mut PayloadBuffer,
|
|
|
|
boundary: &str,
|
2019-11-21 14:25:50 +06:00
|
|
|
) -> Poll<Option<Result<Bytes, MultipartError>>> {
|
2019-04-21 15:41:01 -07:00
|
|
|
let mut pos = 0;
|
|
|
|
|
|
|
|
let len = payload.buf.len();
|
|
|
|
if len == 0 {
|
2019-05-25 03:16:46 -07:00
|
|
|
return if payload.eof {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(Some(Err(MultipartError::Incomplete)))
|
2019-05-25 03:16:46 -07:00
|
|
|
} else {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Pending
|
2019-05-25 03:16:46 -07:00
|
|
|
};
|
2019-04-21 15:41:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// check boundary
|
|
|
|
if len > 4 && payload.buf[0] == b'\r' {
|
|
|
|
let b_len = if &payload.buf[..2] == b"\r\n" && &payload.buf[2..4] == b"--" {
|
|
|
|
Some(4)
|
|
|
|
} else if &payload.buf[1..3] == b"--" {
|
|
|
|
Some(3)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(b_len) = b_len {
|
|
|
|
let b_size = boundary.len() + b_len;
|
|
|
|
if len < b_size {
|
2019-11-21 14:25:50 +06:00
|
|
|
return Poll::Pending;
|
2019-07-17 15:08:30 +06:00
|
|
|
} else if &payload.buf[b_len..b_size] == boundary.as_bytes() {
|
|
|
|
// found boundary
|
2019-11-21 14:25:50 +06:00
|
|
|
return Poll::Ready(None);
|
2019-04-03 12:28:58 -07:00
|
|
|
}
|
|
|
|
}
|
2019-04-21 15:41:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
loop {
|
2022-10-14 17:52:13 +06:00
|
|
|
return if let Some(idx) = memchr::memmem::find(&payload.buf[pos..], b"\r") {
|
2019-04-21 15:41:01 -07:00
|
|
|
let cur = pos + idx;
|
|
|
|
|
|
|
|
// check if we have enough data for boundary detection
|
|
|
|
if cur + 4 > len {
|
|
|
|
if cur > 0 {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(Some(Ok(payload.buf.split_to(cur).freeze())))
|
2019-04-21 15:41:01 -07:00
|
|
|
} else {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Pending
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
} else {
|
2019-04-21 15:41:01 -07:00
|
|
|
// check boundary
|
|
|
|
if (&payload.buf[cur..cur + 2] == b"\r\n"
|
|
|
|
&& &payload.buf[cur + 2..cur + 4] == b"--")
|
2019-07-17 15:08:30 +06:00
|
|
|
|| (&payload.buf[cur..=cur] == b"\r"
|
2019-04-21 15:41:01 -07:00
|
|
|
&& &payload.buf[cur + 1..cur + 3] == b"--")
|
|
|
|
{
|
|
|
|
if cur != 0 {
|
|
|
|
// return buffer
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(Some(Ok(payload.buf.split_to(cur).freeze())))
|
2019-04-21 15:41:01 -07:00
|
|
|
} else {
|
|
|
|
pos = cur + 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// not boundary
|
|
|
|
pos = cur + 1;
|
|
|
|
continue;
|
|
|
|
}
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2019-04-21 15:41:01 -07:00
|
|
|
} else {
|
2019-12-05 23:35:43 +06:00
|
|
|
Poll::Ready(Some(Ok(payload.buf.split().freeze())))
|
2019-04-21 15:41:01 -07:00
|
|
|
};
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 14:25:50 +06:00
|
|
|
fn poll(&mut self, s: &Safety) -> Poll<Option<Result<Bytes, MultipartError>>> {
|
2019-03-28 05:04:39 -07:00
|
|
|
if self.payload.is_none() {
|
2019-11-21 14:25:50 +06:00
|
|
|
return Poll::Ready(None);
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
|
2024-06-10 21:51:53 +01:00
|
|
|
let result = if let Some(mut payload) = self
|
|
|
|
.payload
|
|
|
|
.as_ref()
|
|
|
|
.expect("Field should not be polled after completion")
|
|
|
|
.get_mut(s)
|
|
|
|
{
|
2019-04-21 15:41:01 -07:00
|
|
|
if !self.eof {
|
|
|
|
let res = if let Some(ref mut len) = self.length {
|
2022-09-25 20:54:17 +01:00
|
|
|
InnerField::read_len(&mut payload, len)
|
2019-04-21 15:41:01 -07:00
|
|
|
} else {
|
2022-09-25 20:54:17 +01:00
|
|
|
InnerField::read_stream(&mut payload, &self.boundary)
|
2019-04-21 15:41:01 -07:00
|
|
|
};
|
2019-03-28 05:04:39 -07:00
|
|
|
|
2019-04-21 15:41:01 -07:00
|
|
|
match res {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Pending => return Poll::Pending,
|
|
|
|
Poll::Ready(Some(Ok(bytes))) => return Poll::Ready(Some(Ok(bytes))),
|
2023-02-26 03:26:06 +00:00
|
|
|
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(None) => self.eof = true,
|
2019-04-21 15:41:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-21 14:25:50 +06:00
|
|
|
match payload.readline() {
|
2019-12-12 02:03:44 +01:00
|
|
|
Ok(None) => Poll::Pending,
|
2019-11-21 14:25:50 +06:00
|
|
|
Ok(Some(line)) => {
|
2019-04-21 15:41:01 -07:00
|
|
|
if line.as_ref() != b"\r\n" {
|
2023-07-17 02:38:12 +01:00
|
|
|
log::warn!("multipart field did not read all the data or it is malformed");
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Ready(None)
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2023-02-26 03:26:06 +00:00
|
|
|
Err(err) => Poll::Ready(Some(Err(err))),
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
} else {
|
2019-11-21 14:25:50 +06:00
|
|
|
Poll::Pending
|
2019-03-28 05:04:39 -07:00
|
|
|
};
|
|
|
|
|
2019-11-21 14:25:50 +06:00
|
|
|
if let Poll::Ready(None) = result {
|
2024-06-10 21:51:53 +01:00
|
|
|
// drop payload buffer and make future un-poll-able
|
|
|
|
let _ = self.payload.take();
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2024-06-10 21:51:53 +01:00
|
|
|
|
2019-11-21 14:25:50 +06:00
|
|
|
result
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-02-26 03:26:06 +00:00
|
|
|
use std::time::Duration;
|
2019-11-21 14:25:50 +06:00
|
|
|
|
2023-02-26 03:26:06 +00:00
|
|
|
use actix_http::h1;
|
|
|
|
use actix_web::{
|
|
|
|
http::header::{DispositionParam, DispositionType},
|
|
|
|
rt,
|
|
|
|
test::TestRequest,
|
2024-07-04 00:37:25 +01:00
|
|
|
web::{BufMut as _, BytesMut},
|
2023-02-26 03:26:06 +00:00
|
|
|
FromRequest,
|
|
|
|
};
|
2024-07-01 03:55:08 +01:00
|
|
|
use assert_matches::assert_matches;
|
2022-12-18 01:34:48 +00:00
|
|
|
use futures_util::{future::lazy, StreamExt as _};
|
2021-02-24 09:08:56 +00:00
|
|
|
use tokio::sync::mpsc;
|
|
|
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
2019-03-28 05:04:39 -07:00
|
|
|
|
2023-02-26 03:26:06 +00:00
|
|
|
use super::*;
|
|
|
|
|
2024-02-14 22:22:07 +00:00
|
|
|
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();
|
2024-07-01 03:55:08 +01:00
|
|
|
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"),
|
|
|
|
);
|
|
|
|
|
2024-07-01 03:55:08 +01:00
|
|
|
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"),
|
|
|
|
);
|
2024-07-01 03:55:08 +01:00
|
|
|
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!(
|
2024-07-01 03:55:08 +01:00
|
|
|
Multipart::find_ct_and_boundary(&headers).unwrap().1,
|
|
|
|
"5c02368e880e436dab70ed54e1c58209",
|
2019-03-28 05:04:39 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_stream() -> (
|
2021-02-24 09:08:56 +00:00
|
|
|
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
|
|
|
) {
|
2021-02-24 09:08:56 +00:00
|
|
|
let (tx, rx) = mpsc::unbounded_channel();
|
2019-03-28 05:04:39 -07:00
|
|
|
|
2021-02-24 09:08:56 +00:00
|
|
|
(
|
|
|
|
tx,
|
|
|
|
UnboundedReceiverStream::new(rx).map(|res| res.map_err(|_| panic!())),
|
|
|
|
)
|
2019-03-28 05:04:39 -07:00
|
|
|
}
|
2021-02-24 09:08:56 +00:00
|
|
|
|
2019-12-12 02:03:44 +01: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 {
|
2020-07-22 08:28:33 +09:00
|
|
|
SlowStream {
|
|
|
|
bytes,
|
2019-12-12 02:03:44 +01:00
|
|
|
pos: 0,
|
|
|
|
ready: false,
|
2020-07-22 08:28:33 +09:00
|
|
|
}
|
2019-12-12 02:03:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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>> {
|
2019-12-12 02:03:44 +01:00
|
|
|
let this = self.get_mut();
|
|
|
|
if !this.ready {
|
|
|
|
this.ready = true;
|
|
|
|
cx.waker().wake_by_ref();
|
|
|
|
return Poll::Pending;
|
|
|
|
}
|
2021-02-24 09:08:56 +00:00
|
|
|
|
2019-12-12 02:03:44 +01:00
|
|
|
if this.pos == this.bytes.len() {
|
|
|
|
return Poll::Ready(None);
|
|
|
|
}
|
2021-02-24 09:08:56 +00:00
|
|
|
|
2019-12-12 02:03:44 +01:00
|
|
|
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
|
|
|
|
2019-09-09 17:58:00 +10:00
|
|
|
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
|
2024-02-14 22:22:07 +00:00
|
|
|
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) {
|
2019-09-09 17:58:00 +10:00
|
|
|
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\
|
2021-11-17 17:43:24 +00:00
|
|
|
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",
|
2019-09-09 17:58:00 +10:00
|
|
|
);
|
|
|
|
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();
|
2024-02-14 22:22:07 +00:00
|
|
|
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-09-09 17:58:00 +10:00
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
sender.send(Ok(bytes_stripped)).unwrap();
|
|
|
|
drop(sender); // eof
|
2019-09-09 17:58:00 +10:00
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
let mut multipart = Multipart::new(&headers, payload);
|
2019-09-09 17:58:00 +10:00
|
|
|
|
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-09-09 17:58:00 +10:00
|
|
|
|
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-09-09 17:58:00 +10: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-09-09 17:58:00 +10:00
|
|
|
}
|
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_multipart() {
|
|
|
|
let (sender, payload) = create_stream();
|
2024-02-14 22:22:07 +00:00
|
|
|
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)) => {
|
2024-07-01 03:55:08 +01:00
|
|
|
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
|
|
|
|
2022-09-23 18:06:40 +01: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) => {
|
2022-09-23 18:06:40 +01: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 {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2019-12-12 02:03:44 +01: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() {
|
2024-02-14 22:22:07 +00:00
|
|
|
let (bytes, headers) = create_double_request_with_header();
|
2019-12-12 02:03:44 +01:00
|
|
|
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) => {
|
2024-07-01 03:55:08 +01:00
|
|
|
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
|
|
|
|
2022-09-23 18:06:40 +01: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
|
|
|
|
2019-12-12 02:03:44 +01: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)) => {
|
2022-09-23 18:06:40 +01: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
|
|
|
|
2019-12-12 02:03:44 +01: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-04-03 12:28:58 -07:00
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_basic() {
|
2023-02-26 03:26:06 +00:00
|
|
|
let (_, payload) = h1::Payload::create(false);
|
2019-11-26 11:25:50 +06:00
|
|
|
let mut payload = PayloadBuffer::new(payload);
|
2019-04-03 12:28:58 -07:00
|
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
}
|
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_eof() {
|
2023-02-26 03:26:06 +00:00
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
}
|
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_err() {
|
2023-02-26 03:26:06 +00:00
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
}
|
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_readmax() {
|
2023-02-26 03:26:06 +00:00
|
|
|
let (mut sender, payload) = h1::Payload::create(false);
|
2019-11-26 11:25:50 +06:00
|
|
|
let mut payload = PayloadBuffer::new(payload);
|
2019-04-03 12:28:58 -07:00
|
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
}
|
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_readexactly() {
|
2023-02-26 03:26:06 +00:00
|
|
|
let (mut sender, payload) = h1::Payload::create(false);
|
2019-11-26 11:25:50 +06:00
|
|
|
let mut payload = PayloadBuffer::new(payload);
|
2019-04-03 12:28:58 -07:00
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
assert_eq!(None, payload.read_exact(2));
|
2019-04-03 12:28:58 -07:00
|
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
|
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-04-03 12:28:58 -07:00
|
|
|
}
|
|
|
|
|
2019-11-26 11:25:50 +06:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_readuntil() {
|
2023-02-26 03:26:06 +00:00
|
|
|
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);
|
2019-04-03 12:28:58 -07:00
|
|
|
}
|
2020-09-25 15:50:37 +02:00
|
|
|
|
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_multipart_from_error() {
|
2024-07-01 03:55:08 +01:00
|
|
|
let err = MultipartError::ContentTypeMissing;
|
2020-09-25 15:50:37 +02:00
|
|
|
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();
|
2024-07-01 03:55:08 +01:00
|
|
|
let (ct, boundary) = Multipart::find_ct_and_boundary(&headers).unwrap();
|
|
|
|
let _ = Multipart::from_ct_and_boundary(ct, boundary, payload);
|
2020-09-25 15:50:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_multipart_payload_consumption() {
|
|
|
|
// with sample payload and HttpRequest with no headers
|
2023-02-26 03:26:06 +00:00
|
|
|
let (_, inner_payload) = h1::Payload::create(false);
|
2020-09-25 15:50:37 +02:00
|
|
|
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 {
|
2021-12-24 17:47:47 +00:00
|
|
|
actix_web::dev::Payload::H1 { .. } => {} //expected
|
2020-09-25 15:50:37 +02:00
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
|
|
|
}
|
2021-11-17 17:43:24 +00:00
|
|
|
|
|
|
|
#[actix_rt::test]
|
2024-07-01 03:55:08 +01:00
|
|
|
async fn no_content_disposition_form_data() {
|
2021-11-17 17:43:24 +00:00
|
|
|
let bytes = Bytes::from(
|
|
|
|
"testasdadsad\r\n\
|
|
|
|
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
2024-07-01 03:55:08 +01:00
|
|
|
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\
|
2021-11-17 17:43:24 +00:00
|
|
|
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();
|
2024-07-01 03:55:08 +01:00
|
|
|
res.unwrap();
|
2021-11-17 17:43:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[actix_rt::test]
|
2024-07-01 03:55:08 +01:00
|
|
|
async fn no_name_in_form_data_content_disposition() {
|
2021-11-17 17:43:24 +00:00
|
|
|
let bytes = Bytes::from(
|
|
|
|
"testasdadsad\r\n\
|
|
|
|
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
|
|
|
|
Content-Disposition: form-data; filename=\"fn.txt\"\r\n\
|
2024-07-01 03:55:08 +01:00
|
|
|
Content-Type: text/plain; charset=utf-8\r\n\
|
|
|
|
Content-Length: 4\r\n\
|
|
|
|
\r\n\
|
2021-11-17 17:43:24 +00:00
|
|
|
test\r\n\
|
|
|
|
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n",
|
|
|
|
);
|
|
|
|
let mut headers = HeaderMap::new();
|
|
|
|
headers.insert(
|
|
|
|
header::CONTENT_TYPE,
|
|
|
|
header::HeaderValue::from_static(
|
2024-07-01 03:55:08 +01:00
|
|
|
"multipart/form-data; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
|
2021-11-17 17:43:24 +00:00
|
|
|
),
|
|
|
|
);
|
|
|
|
let payload = SlowStream::new(bytes);
|
|
|
|
|
|
|
|
let mut multipart = Multipart::new(&headers, payload);
|
|
|
|
let res = multipart.next().await.unwrap();
|
2024-07-01 03:55:08 +01:00
|
|
|
assert_matches!(
|
|
|
|
res.expect_err("according to RFC 7578, form-data fields require a name attribute"),
|
|
|
|
MultipartError::ContentDispositionNameMissing
|
|
|
|
);
|
2021-11-17 17:43:24 +00:00
|
|
|
}
|
2021-11-29 05:00:24 +03:00
|
|
|
|
|
|
|
#[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();
|
2024-02-14 22:22:07 +00:00
|
|
|
let (bytes, headers) = create_double_request_with_header();
|
2021-11-29 05:00:24 +03:00
|
|
|
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 {
|
2024-07-01 03:55:08 +01:00
|
|
|
rt::time::sleep(Duration::from_millis(500)).await;
|
2021-11-29 05:00:24 +03:00
|
|
|
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
|
|
|
}
|