Don't force smuggling of user's error inside actix_web::Error

This commit is contained in:
Aode (Lion) 2021-09-13 20:13:48 -05:00
parent 38e6a890f3
commit 1e635ce27c
8 changed files with 153 additions and 116 deletions

View file

@ -1,7 +1,7 @@
[package]
name = "actix-form-data"
description = "Multipart Form Data for Actix Web"
version = "0.6.0-beta.7"
version = "0.6.0-beta.8"
license = "GPL-3.0"
authors = ["asonix <asonix@asonix.dog>"]
repository = "https://git.asonix.dog/Aardwolf/actix-form-data.git"

View file

@ -1,10 +1,24 @@
use actix_form_data::{Error, Field, Form, Value};
use actix_form_data::{Field, Form, Value};
use actix_web::{
web::{post, resource},
App, HttpResponse, HttpServer,
App, HttpResponse, HttpServer, ResponseError,
};
use futures_util::stream::StreamExt;
#[derive(Debug, thiserror::Error)]
#[error("{inner}")]
struct MyError {
inner: Box<dyn std::error::Error + 'static>,
}
impl MyError {
fn new(e: impl std::error::Error + 'static) -> Self {
Self { inner: Box::new(e) }
}
}
impl ResponseError for MyError {}
async fn upload(uploaded_content: Value<()>) -> HttpResponse {
println!("Uploaded Content: {:#?}", uploaded_content);
HttpResponse::Created().finish()
@ -25,9 +39,9 @@ async fn main() -> Result<(), anyhow::Error> {
"files",
Field::array(Field::file(|_, _, mut stream| async move {
while let Some(res) = stream.next().await {
res?;
res.map_err(MyError::new)?;
}
Ok(()) as Result<(), Error>
Ok(()) as Result<(), MyError>
})),
);

View file

@ -19,7 +19,7 @@ use tracing::info;
#[derive(Clone, Debug)]
struct AppState {
form: Form<PathBuf>,
form: Form<PathBuf, Errors>,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
@ -67,7 +67,7 @@ async fn upload(uploaded_content: Value<PathBuf>) -> HttpResponse {
}
async fn save_file(
stream: Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>,
stream: Pin<Box<dyn Stream<Item = Result<Bytes, Error<Errors>>>>>,
count: usize,
) -> Result<String, JsonError> {
use futures_lite::io::AsyncWriteExt;

View file

@ -30,9 +30,9 @@ use actix_web::{
};
#[derive(Debug, thiserror::Error)]
pub enum Error {
pub enum Error<E> {
#[error("{0}")]
FileFn(#[from] actix_web::Error),
FileFn(E),
#[error("Error parsing payload, {0}")]
Payload(#[from] PayloadError),
#[error("Error in multipart creation, {0}")]
@ -61,32 +61,30 @@ pub enum Error {
FileCount,
#[error("File too large")]
FileSize,
#[error("Uploaded guard used without Multipart middleware")]
MissingMiddleware,
#[error("Impossible Error! Middleware exists, didn't fail, and didn't send value")]
TxDropped,
}
impl From<MultipartError> for Error {
impl<E> From<MultipartError> for Error<E> {
fn from(m: MultipartError) -> Self {
Error::Multipart(m)
}
}
impl ResponseError for Error {
impl<E> ResponseError for Error<E>
where
E: ResponseError,
{
fn status_code(&self) -> StatusCode {
match *self {
Error::FileFn(ref e) => ResponseError::status_code(e.as_response_error()),
Error::Payload(ref e) => ResponseError::status_code(e),
Error::MissingMiddleware | Error::TxDropped => StatusCode::INTERNAL_SERVER_ERROR,
Error::FileFn(ref e) => e.status_code(),
Error::Payload(ref e) => e.status_code(),
_ => StatusCode::BAD_REQUEST,
}
}
fn error_response(&self) -> HttpResponse {
match *self {
Error::FileFn(ref e) => ResponseError::error_response(e.as_response_error()),
Error::Payload(ref e) => ResponseError::error_response(e),
Error::FileFn(ref e) => e.error_response(),
Error::Payload(ref e) => e.error_response(),
Error::Multipart(_)
| Error::ParseField(_)
| Error::ParseInt(_)
@ -100,9 +98,6 @@ impl ResponseError for Error {
| Error::Filename
| Error::FileCount
| Error::FileSize => HttpResponse::BadRequest().finish(),
Error::MissingMiddleware | Error::TxDropped => {
HttpResponse::InternalServerError().finish()
}
}
}
}

View file

@ -54,7 +54,7 @@
//! while let Some(_) = stream.next().await {
//! // do something
//! }
//! Ok(()) as Result<(), std::convert::Infallible>
//! Ok(()) as Result<(), std::io::Error>
//! })),
//! );
//!

View file

@ -18,13 +18,13 @@
*/
use crate::{
error::Error,
types::{Form, Value},
upload::handle_multipart,
};
use actix_web::{
dev::{Payload, Service, ServiceRequest, Transform},
FromRequest, HttpMessage, HttpRequest,
http::StatusCode,
FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError,
};
use futures_util::future::LocalBoxFuture;
use std::{
@ -33,12 +33,36 @@ use std::{
};
use tokio::sync::oneshot::{channel, Receiver};
#[derive(Debug, thiserror::Error)]
pub enum FromRequestError {
#[error("Uploaded guard used without Multipart middleware")]
MissingMiddleware,
#[error("Impossible Error! Middleware exists, didn't fail, and didn't send value")]
TxDropped,
}
impl ResponseError for FromRequestError {
fn status_code(&self) -> StatusCode {
match self {
Self::MissingMiddleware | Self::TxDropped => StatusCode::INTERNAL_SERVER_ERROR,
}
}
fn error_response(&self) -> HttpResponse {
match self {
Self::MissingMiddleware | Self::TxDropped => {
HttpResponse::InternalServerError().finish()
}
}
}
}
struct Uploaded<T> {
rx: Receiver<Value<T>>,
}
pub struct MultipartMiddleware<S, T> {
form: Form<T>,
pub struct MultipartMiddleware<S, T, E> {
form: Form<T, E>,
service: S,
}
@ -46,30 +70,31 @@ impl<T> FromRequest for Value<T>
where
T: 'static,
{
type Error = Error;
type Error = FromRequestError;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let opt = req.extensions_mut().remove::<Uploaded<T>>();
Box::pin(async move {
let fut = opt.ok_or(Error::MissingMiddleware)?;
let fut = opt.ok_or(FromRequestError::MissingMiddleware)?;
fut.rx.await.map_err(|_| Error::TxDropped)
fut.rx.await.map_err(|_| FromRequestError::TxDropped)
})
}
}
impl<S, T> Transform<S, ServiceRequest> for Form<T>
impl<S, T, E> Transform<S, ServiceRequest> for Form<T, E>
where
S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static,
T: 'static,
E: ResponseError + 'static,
{
type Response = S::Response;
type Error = S::Error;
type InitError = ();
type Transform = MultipartMiddleware<S, T>;
type Transform = MultipartMiddleware<S, T, E>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
@ -80,11 +105,12 @@ where
}
}
impl<S, T> Service<ServiceRequest> for MultipartMiddleware<S, T>
impl<S, T, E> Service<ServiceRequest> for MultipartMiddleware<S, T, E>
where
S: Service<ServiceRequest, Error = actix_web::Error>,
S::Future: 'static,
T: 'static,
E: ResponseError + 'static,
{
type Response = S::Response;
type Error = S::Error;

View file

@ -31,7 +31,7 @@ use std::{
use tracing::trace;
#[derive(Debug)]
pub struct FileMeta<T = ()> {
pub struct FileMeta<T> {
pub filename: String,
pub content_type: Mime,
pub result: T,
@ -56,7 +56,7 @@ pub struct FileMeta<T = ()> {
/// }
/// ```
#[derive(Debug)]
pub enum Value<T = ()> {
pub enum Value<T> {
Map(HashMap<String, Value<T>>),
Array(Vec<Value<T>>),
File(FileMeta<T>),
@ -153,28 +153,28 @@ impl<T> From<MultipartContent<T>> for Value<T> {
}
}
pub type FileFn<T> = Arc<
pub type FileFn<T, E> = Arc<
dyn Fn(
String,
Mime,
Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>,
) -> Pin<Box<dyn Future<Output = Result<T, actix_web::Error>>>>
Pin<Box<dyn Stream<Item = Result<Bytes, Error<E>>>>>,
) -> Pin<Box<dyn Future<Output = Result<T, Error<E>>>>>
+ Send
+ Sync,
>;
/// The field type represents a field in the form-data that is allowed to be parsed.
pub enum Field<T = ()> {
Array(Array<T>),
Map(Map<T>),
File(FileFn<T>),
pub enum Field<T, E> {
Array(Array<T, E>),
Map(Map<T, E>),
File(FileFn<T, E>),
Bytes,
Int,
Float,
Text,
}
impl<T> Clone for Field<T> {
impl<T, E> Clone for Field<T, E> {
fn clone(&self) -> Self {
match self {
Self::Array(a) => Self::Array(a.clone()),
@ -188,7 +188,7 @@ impl<T> Clone for Field<T> {
}
}
impl<T> fmt::Debug for Field<T> {
impl<T, E> fmt::Debug for Field<T, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Field::Array(ref arr) => f.debug_tuple("Array").field(arr).finish(),
@ -202,7 +202,7 @@ impl<T> fmt::Debug for Field<T> {
}
}
impl<T> Field<T> {
impl<T, E> Field<T, E> {
/// Add a File field with a name generator.
///
/// The name generator will be called for each file matching this field's key. Keep in mind
@ -216,7 +216,7 @@ impl<T> Field<T> {
/// # use futures_util::stream::StreamExt;
/// #
/// let (tx, rx) = channel(1);
/// let form = Form::<()>::new().field("file-field", Field::file(move |_, _, mut stream| {
/// let form = Form::new().field("file-field", Field::file(move |_, _, mut stream| {
/// let mut tx = tx.clone();
/// async move {
/// while let Some(res) = stream.next().await {
@ -226,23 +226,23 @@ impl<T> Field<T> {
/// }
/// }
/// }
/// Ok(()) as Result<_, std::convert::Infallible>
/// Ok(()) as Result<_, std::io::Error>
/// }
/// }));
/// ```
pub fn file<F, Fut, E>(f: F) -> Self
pub fn file<F, Fut>(f: F) -> Self
where
F: Fn(String, Mime, Pin<Box<dyn Stream<Item = Result<Bytes, Error>>>>) -> Fut
F: Fn(String, Mime, Pin<Box<dyn Stream<Item = Result<Bytes, Error<E>>>>>) -> Fut
+ Send
+ Sync
+ Clone
+ 'static,
Fut: Future<Output = Result<T, E>> + 'static,
E: Into<actix_web::Error> + 'static,
E: 'static,
{
Field::File(Arc::new(move |filename, mime, stream| {
let f = f.clone();
Box::pin(async move { (f)(filename, mime, stream).await.map_err(|e| e.into()) })
Box::pin(async move { (f)(filename, mime, stream).await.map_err(Error::FileFn) })
}))
}
@ -251,7 +251,7 @@ impl<T> Field<T> {
/// # Example
/// ```rust
/// # use actix_form_data::{Form, Field};
/// let form = Form::<()>::new().field("text-field", Field::bytes());
/// let form = Form::<(), std::io::Error>::new().field("text-field", Field::bytes());
pub fn bytes() -> Self {
Field::Bytes
}
@ -261,7 +261,7 @@ impl<T> Field<T> {
/// # Example
/// ```rust
/// # use actix_form_data::{Form, Field};
/// let form = Form::<()>::new().field("text-field", Field::text());
/// let form = Form::<(), std::io::Error>::new().field("text-field", Field::text());
pub fn text() -> Self {
Field::Text
}
@ -271,7 +271,7 @@ impl<T> Field<T> {
/// # Example
/// ```rust
/// # use actix_form_data::{Form, Field};
/// let form = Form::<()>::new().field("int-field", Field::int());
/// let form = Form::<(), std::io::Error>::new().field("int-field", Field::int());
/// ```
pub fn int() -> Self {
Field::Int
@ -282,7 +282,7 @@ impl<T> Field<T> {
/// # Example
/// ```rust
/// # use actix_form_data::{Form, Field};
/// let form = Form::<()>::new().field("float-field", Field::float());
/// let form = Form::<(), std::io::Error>::new().field("float-field", Field::float());
/// ```
pub fn float() -> Self {
Field::Float
@ -294,14 +294,14 @@ impl<T> Field<T> {
/// ```rust
/// # use actix_form_data::{Form, Field};
/// # fn main() {
/// let form = Form::<()>::new()
/// let form = Form::<(), std::io::Error>::new()
/// .field(
/// "array-field",
/// Field::array(Field::text())
/// );
/// # }
/// ```
pub fn array(field: Field<T>) -> Self {
pub fn array(field: Field<T, E>) -> Self {
Field::Array(Array::new(field))
}
@ -311,7 +311,7 @@ impl<T> Field<T> {
/// ```rust
/// # use actix_form_data::{Form, Field};
/// # fn main() {
/// let form = Form::<()>::new()
/// let form = Form::<(), std::io::Error>::new()
/// .field(
/// "map-field",
/// Field::map()
@ -321,11 +321,11 @@ impl<T> Field<T> {
/// );
/// # }
/// ```
pub fn map() -> Map<T> {
pub fn map() -> Map<T, E> {
Map::new()
}
fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
trace!("Checking {:?} and {:?}", self, name);
match *self {
Field::Array(ref arr) => arr.valid_field(name),
@ -373,11 +373,11 @@ impl<T> Field<T> {
///
/// The `Array` type should only be constructed in the context of a Form. See the `Form`
/// documentation for more information.
pub struct Array<T = ()> {
inner: Box<Field<T>>,
pub struct Array<T, E> {
inner: Box<Field<T, E>>,
}
impl<T> Clone for Array<T> {
impl<T, E> Clone for Array<T, E> {
fn clone(&self) -> Self {
Array {
inner: Box::new((*self.inner).clone()),
@ -385,20 +385,20 @@ impl<T> Clone for Array<T> {
}
}
impl<T> fmt::Debug for Array<T> {
impl<T, E> fmt::Debug for Array<T, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Array").field("inner", &self.inner).finish()
}
}
impl<T> Array<T> {
fn new(field: Field<T>) -> Self {
impl<T, E> Array<T, E> {
fn new(field: Field<T, E>) -> Self {
Array {
inner: Box::new(field),
}
}
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
trace!("Checking {:?} and {:?}", self, name);
match name.pop_front() {
Some(NamePart::Array) => self.inner.valid_field(name),
@ -408,11 +408,11 @@ impl<T> Array<T> {
}
/// A definition of key-value pairs to be parsed from form data.
pub struct Map<T = ()> {
inner: Vec<(String, Field<T>)>,
pub struct Map<T, E> {
inner: Vec<(String, Field<T, E>)>,
}
impl<T> Clone for Map<T> {
impl<T, E> Clone for Map<T, E> {
fn clone(&self) -> Self {
Map {
inner: self.inner.clone(),
@ -420,13 +420,13 @@ impl<T> Clone for Map<T> {
}
}
impl<T> fmt::Debug for Map<T> {
impl<T, E> fmt::Debug for Map<T, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Map").field("inner", &self.inner).finish()
}
}
impl<T> Map<T> {
impl<T, E> Map<T, E> {
fn new() -> Self {
Map { inner: Vec::new() }
}
@ -436,12 +436,12 @@ impl<T> Map<T> {
/// ```rust
/// # use actix_form_data::Field;
/// #
/// Field::<()>::map()
/// Field::<(), std::io::Error>::map()
/// .field("sub-field", Field::text())
/// .field("sub-field-two", Field::text())
/// .finalize();
/// ```
pub fn field(mut self, key: &str, value: Field<T>) -> Self {
pub fn field(mut self, key: &str, value: Field<T, E>) -> Self {
self.inner.push((key.to_owned(), value));
self
@ -451,16 +451,16 @@ impl<T> Map<T> {
/// ```rust
/// # use actix_form_data::Field;
/// #
/// Field::<()>::map()
/// Field::<(), std::io::Error>::map()
/// .field("sub-field", Field::text())
/// .field("sub-field-two", Field::text())
/// .finalize();
/// ```
pub fn finalize(self) -> Field<T> {
pub fn finalize(self) -> Field<T, E> {
Field::Map(self)
}
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
fn valid_field(&self, mut name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
trace!("Checking {:?} and {:?}", self, name);
match name.pop_front() {
Some(NamePart::Map(name_part)) => self
@ -478,12 +478,12 @@ impl<T> Map<T> {
/// # Example
/// ```rust
/// # use actix_form_data::{Form, Field};
/// let form = Form::<()>::new()
/// let form = Form::<(), std::io::Error>::new()
/// .field("field-name", Field::text())
/// .field("second-field", Field::int())
/// .field("third-field", Field::float())
/// .field("fifth-field", Field::file(|_, _, _| async move {
/// Ok(()) as Result<(), std::convert::Infallible>
/// Ok(())
/// }))
/// .field(
/// "map-field",
@ -497,36 +497,35 @@ impl<T> Map<T> {
/// Field::array(Field::text())
/// );
/// ```
pub struct Form<T = ()> {
pub struct Form<T, E> {
pub(crate) max_fields: u32,
pub(crate) max_field_size: usize,
pub(crate) max_files: u32,
pub(crate) max_file_size: usize,
pub(crate) transform_error:
Option<Arc<dyn Fn(crate::error::Error) -> actix_web::Error + Send + Sync>>,
inner: Map<T>,
pub(crate) transform_error: Option<Arc<dyn Fn(Error<E>) -> actix_web::Error + Send + Sync>>,
inner: Map<T, E>,
}
impl<T> Clone for Form<T> {
impl<T, E> Clone for Form<T, E> {
fn clone(&self) -> Self {
Form {
max_fields: self.max_fields,
max_field_size: self.max_field_size,
max_files: self.max_files,
max_file_size: self.max_file_size,
transform_error: self.transform_error.clone(),
transform_error: None,
inner: self.inner.clone(),
}
}
}
impl<T> Default for Form<T> {
impl<T, E> Default for Form<T, E> {
fn default() -> Self {
Self::new()
}
}
impl<T> Form<T> {
impl<T, E> Form<T, E> {
/// Create a new form
///
/// If you wish to provide your own executor, use the `with_executor` method.
@ -547,13 +546,12 @@ impl<T> Form<T> {
}
}
/// Add an optional error handler to transform errors produced by the middleware
/// Set the Transform Error method to convert Error types into actix_web::Error by hand
pub fn transform_error(
mut self,
f: impl Fn(crate::error::Error) -> actix_web::Error + Send + Sync + 'static,
f: impl Fn(Error<E>) -> actix_web::Error + Send + Sync + 'static,
) -> Self {
self.transform_error = Some(Arc::new(f));
self
}
@ -593,18 +591,18 @@ impl<T> Form<T> {
self
}
pub fn field(mut self, name: &str, field: Field<T>) -> Self {
pub fn field(mut self, name: &str, field: Field<T, E>) -> Self {
self.inner = self.inner.field(name, field);
self
}
pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T>> {
pub(crate) fn valid_field(&self, name: VecDeque<&NamePart>) -> Option<FieldTerminator<T, E>> {
self.inner.valid_field(name.clone())
}
}
impl<T> fmt::Debug for Form<T> {
impl<T, E> fmt::Debug for Form<T, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Form").field("inner", &self.inner).finish()
}
@ -638,15 +636,15 @@ impl NamePart {
}
#[derive(Clone)]
pub(crate) enum FieldTerminator<T = ()> {
File(FileFn<T>),
pub(crate) enum FieldTerminator<T, E> {
File(FileFn<T, E>),
Bytes,
Int,
Float,
Text,
}
impl<T> fmt::Debug for FieldTerminator<T> {
impl<T, E> fmt::Debug for FieldTerminator<T, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
FieldTerminator::File(_) => write!(f, "File"),
@ -662,7 +660,7 @@ pub(crate) type MultipartHash<T> = (Vec<NamePart>, MultipartContent<T>);
pub(crate) type MultipartForm<T> = Vec<MultipartHash<T>>;
#[derive(Debug)]
pub(crate) enum MultipartContent<T = ()> {
pub(crate) enum MultipartContent<T> {
File(FileMeta<T>),
Bytes(Bytes),
Text(String),

View file

@ -65,7 +65,7 @@ fn consolidate<T>(mf: MultipartForm<T>) -> Value<T> {
)
}
fn parse_multipart_name(name: String) -> Result<Vec<NamePart>, Error> {
fn parse_multipart_name<E>(name: String) -> Result<Vec<NamePart>, Error<E>> {
name.split('[')
.map(|part| {
if part.len() == 1 && part.ends_with(']') {
@ -99,14 +99,15 @@ fn parse_content_disposition(field: &actix_multipart::Field) -> ContentDispositi
}
}
async fn handle_file_upload<T>(
async fn handle_file_upload<T, E>(
field: actix_multipart::Field,
filename: Option<String>,
form: Form<T>,
file_fn: FileFn<T>,
) -> Result<MultipartContent<T>, Error>
form: Form<T, E>,
file_fn: FileFn<T, E>,
) -> Result<MultipartContent<T>, Error<E>>
where
T: 'static,
E: 'static,
{
let filename = filename.ok_or(Error::Filename)?;
let path: &Path = filename.as_ref();
@ -150,13 +151,14 @@ where
}))
}
async fn handle_form_data<T>(
async fn handle_form_data<T, E>(
mut field: actix_multipart::Field,
term: FieldTerminator<T>,
form: Form<T>,
) -> Result<MultipartContent<T>, Error>
term: FieldTerminator<T, E>,
form: Form<T, E>,
) -> Result<MultipartContent<T>, Error<E>>
where
T: 'static,
E: 'static,
{
trace!("In handle_form_data, term: {:?}", term);
let mut bytes = BytesMut::new();
@ -190,12 +192,13 @@ where
}
}
async fn handle_stream_field<T>(
async fn handle_stream_field<T, E>(
field: actix_multipart::Field,
form: Form<T>,
) -> Result<MultipartHash<T>, Error>
form: Form<T, E>,
) -> Result<MultipartHash<T>, Error<E>>
where
T: 'static,
E: 'static,
{
let content_disposition = parse_content_disposition(&field);
@ -217,12 +220,13 @@ where
}
/// Handle multipart streams from Actix Web
pub async fn handle_multipart<T>(
pub async fn handle_multipart<T, E>(
m: actix_multipart::Multipart,
form: Form<T>,
) -> Result<Value<T>, Error>
form: Form<T, E>,
) -> Result<Value<T>, Error<E>>
where
T: 'static,
E: 'static,
{
let mut multipart_form = Vec::new();
let mut file_count: u32 = 0;
@ -257,12 +261,12 @@ where
Ok(consolidate(multipart_form))
}
fn count<T>(
fn count<T, E>(
content: &MultipartContent<T>,
mut file_count: u32,
mut field_count: u32,
form: &Form<T>,
) -> Result<(u32, u32), Error> {
form: &Form<T, E>,
) -> Result<(u32, u32), Error<E>> {
match content {
MultipartContent::File(_) => {
file_count += 1;