Skip to content

Commit

Permalink
[Rust Server] Allow configuration of multipart/form attachment size l…
Browse files Browse the repository at this point in the history
…imit (#19371)

* [Rust Server] Allow configuration of multipart/form attachment size limit

multipart 0.14+ imposes a 8MB size limit on multipart/form bodies.

This allows that limit to be configured. The default is left as is.

This also improves error messages produced when handling multipart/form bodies.

* Update samples
  • Loading branch information
richardwhiuk committed Aug 17, 2024
1 parent bb831da commit 0a5c997
Show file tree
Hide file tree
Showing 13 changed files with 440 additions and 180 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use mime_multipart::{read_multipart_body, Node, Part};
{{/apiUsesMultipartRelated}}
{{#apiUsesMultipartFormData}}
use multipart::server::Multipart;
use multipart::server::save::SaveResult;
use multipart::server::save::{PartialReason, SaveResult};
{{/apiUsesMultipartFormData}}

#[allow(unused_imports)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pub struct MakeService<T, C> where
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
{
api_impl: T,
{{#apiUsesMultipartFormData}}
multipart_form_size_limit: Option<u64>,
{{/apiUsesMultipartFormData}}
marker: PhantomData<C>,
}

Expand All @@ -14,11 +17,25 @@ impl<T, C> MakeService<T, C> where
pub fn new(api_impl: T) -> Self {
MakeService {
api_impl,
{{#apiUsesMultipartFormData}}
multipart_form_size_limit: Some(8 * 1024 * 1024),
{{/apiUsesMultipartFormData}}
marker: PhantomData
}
}
}
{{#apiUsesMultipartFormData}}

/// Configure size limit when inspecting a multipart/form body.
///
/// Default is 8 MiB.
///
/// Set to None for no size limit, which presents a Denial of Service attack risk.
pub fn multipart_form_size_limit(mut self, multipart_form_size_limit: Option<u64>) -> Self {
self.multipart_form_size_limit = multipart_form_size_limit;
self
}
{{/apiUsesMultipartFormData}}
}

impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static,
Expand All @@ -33,8 +50,11 @@ impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
}
fn call(&mut self, target: Target) -> Self::Future {
future::ok(Service::new(
self.api_impl.clone(),
))
let service = Service::new(self.api_impl.clone()){{^apiUsesMultipartFormData}};{{/apiUsesMultipartFormData}}
{{#apiUsesMultipartFormData}}
.multipart_form_size_limit(self.multipart_form_size_limit);
{{/apiUsesMultipartFormData}}
future::ok(service)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,43 @@
use std::io::Read;

// Read Form Parameters from body
let mut entries = match Multipart::with_body(&body.to_vec()[..], boundary).save().temp() {
let mut entries = match Multipart::with_body(&body.to_vec()[..], boundary)
.save()
.size_limit(multipart_form_size_limit)
.temp()
{
SaveResult::Full(entries) => {
entries
},
_ => {
SaveResult::Partial(_, PartialReason::CountLimit) => {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from("Unable to process all message parts".to_string()))
.expect("Unable to create Bad Request response due to failure to process all message"))
.body(Body::from("Unable to process message part due to excessive parts".to_string()))
.expect("Unable to create Bad Request response due to excessive parts"))
},
SaveResult::Partial(_, PartialReason::SizeLimit) => {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from("Unable to process message part due to excessive data".to_string()))
.expect("Unable to create Bad Request response due to excessive data"))
},
SaveResult::Partial(_, PartialReason::Utf8Error(_)) => {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from("Unable to process message part due to invalid data".to_string()))
.expect("Unable to create Bad Request response due to invalid data"))
},
SaveResult::Partial(_, PartialReason::IoError(_)) => {
return Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("Failed to process message part due an internal error".to_string()))
.expect("Unable to create Internal Server Error response due to an internal errror"))
},
SaveResult::Error(e) => {
return Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("Failed to process all message parts due to an internal error".to_string()))
.expect("Unable to create Internal Server Error response due to an internal error"))
},
};
{{#formParams}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
_ => Ok(Response::builder().status(StatusCode::NOT_FOUND)
.body(Body::empty())
.expect("Unable to create Not Found response"))
_ => Ok(Response::builder().status(StatusCode::NOT_FOUND)
.body(Body::empty())
.expect("Unable to create Not Found response"))
}
}
} Box::pin(run(self.api_impl.clone(), req)) }
Box::pin(run(
self.api_impl.clone(),
req,
{{#apiUsesMultipartFormData}}
self.multipart_form_size_limit,
{{/apiUsesMultipartFormData}}
))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pub struct Service<T, C> where
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
{
api_impl: T,
{{#apiUsesMultipart}}
multipart_form_size_limit: Option<u64>,
{{/apiUsesMultipart}}
marker: PhantomData<C>,
}

Expand All @@ -21,9 +24,24 @@ impl<T, C> Service<T, C> where
pub fn new(api_impl: T) -> Self {
Service {
api_impl,
{{#apiUsesMultipart}}
multipart_form_size_limit: Some(8 * 1024 * 1024),
{{/apiUsesMultipart}}
marker: PhantomData
}
}
{{#apiUsesMultipart}}

/// Configure size limit when extracting a multipart/form body.
///
/// Default is 8 MiB.
///
/// Set to None for no size limit, which presents a Denial of Service attack risk.
pub fn multipart_form_size_limit(mut self, multipart_form_size_limit: Option<u64>) -> Self {
self.multipart_form_size_limit = multipart_form_size_limit;
self
}
{{/apiUsesMultipart}}
}

impl<T, C> Clone for Service<T, C> where
Expand All @@ -33,6 +51,9 @@ impl<T, C> Clone for Service<T, C> where
fn clone(&self) -> Self {
Service {
api_impl: self.api_impl.clone(),
{{#apiUsesMultipart}}
multipart_form_size_limit: Some(8 * 1024 * 1024),
{{/apiUsesMultipart}}
marker: self.marker,
}
}
Expand All @@ -50,17 +71,24 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
self.api_impl.poll_ready(cx)
}

fn call(&mut self, req: (Request<Body>, C)) -> Self::Future { async fn run<T, C>(mut api_impl: T, req: (Request<Body>, C)) -> Result<Response<Body>, crate::ServiceError> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
{
let (request, context) = req;
let (parts, body) = request.into_parts();
let (method, uri, headers) = (parts.method, parts.uri, parts.headers);
let path = paths::GLOBAL_REGEX_SET.matches(uri.path());
fn call(&mut self, req: (Request<Body>, C)) -> Self::Future {
async fn run<T, C>(
mut api_impl: T,
req: (Request<Body>, C),
{{#apiUsesMultipartFormData}}
multipart_form_size_limit: Option<u64>,
{{/apiUsesMultipartFormData}}
) -> Result<Response<Body>, crate::ServiceError> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
{
let (request, context) = req;
let (parts, body) = request.into_parts();
let (method, uri, headers) = (parts.method, parts.uri, parts.headers);
let path = paths::GLOBAL_REGEX_SET.matches(uri.path());
{{!
This match statement is duplicated below in `parse_operation_id()`.
Please update both places if changing how this code is autogenerated.
}}
match method {
{{!
This match statement is duplicated below in `parse_operation_id()`.
Please update both places if changing how this code is autogenerated.
}}
match method {
Loading

0 comments on commit 0a5c997

Please sign in to comment.