Skip to content

Commit 0a5c997

Browse files
authored
[Rust Server] Allow configuration of multipart/form attachment size limit (#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
1 parent bb831da commit 0a5c997

13 files changed

Lines changed: 440 additions & 180 deletions

File tree

modules/openapi-generator/src/main/resources/rust-server/server-imports.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use mime_multipart::{read_multipart_body, Node, Part};
1919
{{/apiUsesMultipartRelated}}
2020
{{#apiUsesMultipartFormData}}
2121
use multipart::server::Multipart;
22-
use multipart::server::save::SaveResult;
22+
use multipart::server::save::{PartialReason, SaveResult};
2323
{{/apiUsesMultipartFormData}}
2424

2525
#[allow(unused_imports)]

modules/openapi-generator/src/main/resources/rust-server/server-make-service.mustache

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ pub struct MakeService<T, C> where
44
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
55
{
66
api_impl: T,
7+
{{#apiUsesMultipartFormData}}
8+
multipart_form_size_limit: Option<u64>,
9+
{{/apiUsesMultipartFormData}}
710
marker: PhantomData<C>,
811
}
912

@@ -14,11 +17,25 @@ impl<T, C> MakeService<T, C> where
1417
pub fn new(api_impl: T) -> Self {
1518
MakeService {
1619
api_impl,
20+
{{#apiUsesMultipartFormData}}
21+
multipart_form_size_limit: Some(8 * 1024 * 1024),
22+
{{/apiUsesMultipartFormData}}
1723
marker: PhantomData
1824
}
1925
}
20-
}
26+
{{#apiUsesMultipartFormData}}
2127

28+
/// Configure size limit when inspecting a multipart/form body.
29+
///
30+
/// Default is 8 MiB.
31+
///
32+
/// Set to None for no size limit, which presents a Denial of Service attack risk.
33+
pub fn multipart_form_size_limit(mut self, multipart_form_size_limit: Option<u64>) -> Self {
34+
self.multipart_form_size_limit = multipart_form_size_limit;
35+
self
36+
}
37+
{{/apiUsesMultipartFormData}}
38+
}
2239

2340
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
2441
T: Api<C> + Clone + Send + 'static,
@@ -33,8 +50,11 @@ impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
3350
}
3451
3552
fn call(&mut self, target: Target) -> Self::Future {
36-
future::ok(Service::new(
37-
self.api_impl.clone(),
38-
))
53+
let service = Service::new(self.api_impl.clone()){{^apiUsesMultipartFormData}};{{/apiUsesMultipartFormData}}
54+
{{#apiUsesMultipartFormData}}
55+
.multipart_form_size_limit(self.multipart_form_size_limit);
56+
{{/apiUsesMultipartFormData}}
57+
58+
future::ok(service)
3959
}
4060
}

modules/openapi-generator/src/main/resources/rust-server/server-request-body-multipart-form.mustache

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,43 @@
99
use std::io::Read;
1010

1111
// Read Form Parameters from body
12-
let mut entries = match Multipart::with_body(&body.to_vec()[..], boundary).save().temp() {
12+
let mut entries = match Multipart::with_body(&body.to_vec()[..], boundary)
13+
.save()
14+
.size_limit(multipart_form_size_limit)
15+
.temp()
16+
{
1317
SaveResult::Full(entries) => {
1418
entries
1519
},
16-
_ => {
20+
SaveResult::Partial(_, PartialReason::CountLimit) => {
1721
return Ok(Response::builder()
1822
.status(StatusCode::BAD_REQUEST)
19-
.body(Body::from("Unable to process all message parts".to_string()))
20-
.expect("Unable to create Bad Request response due to failure to process all message"))
23+
.body(Body::from("Unable to process message part due to excessive parts".to_string()))
24+
.expect("Unable to create Bad Request response due to excessive parts"))
25+
},
26+
SaveResult::Partial(_, PartialReason::SizeLimit) => {
27+
return Ok(Response::builder()
28+
.status(StatusCode::BAD_REQUEST)
29+
.body(Body::from("Unable to process message part due to excessive data".to_string()))
30+
.expect("Unable to create Bad Request response due to excessive data"))
31+
},
32+
SaveResult::Partial(_, PartialReason::Utf8Error(_)) => {
33+
return Ok(Response::builder()
34+
.status(StatusCode::BAD_REQUEST)
35+
.body(Body::from("Unable to process message part due to invalid data".to_string()))
36+
.expect("Unable to create Bad Request response due to invalid data"))
37+
},
38+
SaveResult::Partial(_, PartialReason::IoError(_)) => {
39+
return Ok(Response::builder()
40+
.status(StatusCode::INTERNAL_SERVER_ERROR)
41+
.body(Body::from("Failed to process message part due an internal error".to_string()))
42+
.expect("Unable to create Internal Server Error response due to an internal errror"))
43+
},
44+
SaveResult::Error(e) => {
45+
return Ok(Response::builder()
46+
.status(StatusCode::INTERNAL_SERVER_ERROR)
47+
.body(Body::from("Failed to process all message parts due to an internal error".to_string()))
48+
.expect("Unable to create Internal Server Error response due to an internal error"))
2149
},
2250
};
2351
{{#formParams}}
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
_ => Ok(Response::builder().status(StatusCode::NOT_FOUND)
2-
.body(Body::empty())
3-
.expect("Unable to create Not Found response"))
1+
_ => Ok(Response::builder().status(StatusCode::NOT_FOUND)
2+
.body(Body::empty())
3+
.expect("Unable to create Not Found response"))
4+
}
45
}
5-
} Box::pin(run(self.api_impl.clone(), req)) }
6+
Box::pin(run(
7+
self.api_impl.clone(),
8+
req,
9+
{{#apiUsesMultipartFormData}}
10+
self.multipart_form_size_limit,
11+
{{/apiUsesMultipartFormData}}
12+
))
13+
}
614
}

modules/openapi-generator/src/main/resources/rust-server/server-service-header.mustache

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pub struct Service<T, C> where
1111
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
1212
{
1313
api_impl: T,
14+
{{#apiUsesMultipart}}
15+
multipart_form_size_limit: Option<u64>,
16+
{{/apiUsesMultipart}}
1417
marker: PhantomData<C>,
1518
}
1619

@@ -21,9 +24,24 @@ impl<T, C> Service<T, C> where
2124
pub fn new(api_impl: T) -> Self {
2225
Service {
2326
api_impl,
27+
{{#apiUsesMultipart}}
28+
multipart_form_size_limit: Some(8 * 1024 * 1024),
29+
{{/apiUsesMultipart}}
2430
marker: PhantomData
2531
}
2632
}
33+
{{#apiUsesMultipart}}
34+
35+
/// Configure size limit when extracting a multipart/form body.
36+
///
37+
/// Default is 8 MiB.
38+
///
39+
/// Set to None for no size limit, which presents a Denial of Service attack risk.
40+
pub fn multipart_form_size_limit(mut self, multipart_form_size_limit: Option<u64>) -> Self {
41+
self.multipart_form_size_limit = multipart_form_size_limit;
42+
self
43+
}
44+
{{/apiUsesMultipart}}
2745
}
2846

2947
impl<T, C> Clone for Service<T, C> where
@@ -33,6 +51,9 @@ impl<T, C> Clone for Service<T, C> where
3351
fn clone(&self) -> Self {
3452
Service {
3553
api_impl: self.api_impl.clone(),
54+
{{#apiUsesMultipart}}
55+
multipart_form_size_limit: Some(8 * 1024 * 1024),
56+
{{/apiUsesMultipart}}
3657
marker: self.marker,
3758
}
3859
}
@@ -50,17 +71,24 @@ impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
5071
self.api_impl.poll_ready(cx)
5172
}
5273

53-
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
54-
T: Api<C> + Clone + Send + 'static,
55-
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
56-
{
57-
let (request, context) = req;
58-
let (parts, body) = request.into_parts();
59-
let (method, uri, headers) = (parts.method, parts.uri, parts.headers);
60-
let path = paths::GLOBAL_REGEX_SET.matches(uri.path());
74+
fn call(&mut self, req: (Request<Body>, C)) -> Self::Future {
75+
async fn run<T, C>(
76+
mut api_impl: T,
77+
req: (Request<Body>, C),
78+
{{#apiUsesMultipartFormData}}
79+
multipart_form_size_limit: Option<u64>,
80+
{{/apiUsesMultipartFormData}}
81+
) -> Result<Response<Body>, crate::ServiceError> where
82+
T: Api<C> + Clone + Send + 'static,
83+
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<Authorization>>{{/hasAuthMethods}} + Send + Sync + 'static
84+
{
85+
let (request, context) = req;
86+
let (parts, body) = request.into_parts();
87+
let (method, uri, headers) = (parts.method, parts.uri, parts.headers);
88+
let path = paths::GLOBAL_REGEX_SET.matches(uri.path());
6189
62-
{{!
63-
This match statement is duplicated below in `parse_operation_id()`.
64-
Please update both places if changing how this code is autogenerated.
65-
}}
66-
match method {
90+
{{!
91+
This match statement is duplicated below in `parse_operation_id()`.
92+
Please update both places if changing how this code is autogenerated.
93+
}}
94+
match method {

0 commit comments

Comments
 (0)