Skip to content

Commit e835d54

Browse files
committed
Merge branch 'release/v1.0.2'
2 parents c2c3294 + 491fd3f commit e835d54

File tree

7 files changed

+242
-4
lines changed

7 files changed

+242
-4
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "routerify-multipart"
3-
version = "1.0.1"
3+
version = "1.0.2"
44
description = "A multipart/form-data parser for Routerify."
55
homepage = "https://github.com/routerify/routerify-multipart"
66
repository = "https://github.com/routerify/routerify-multipart"
@@ -25,7 +25,7 @@ json = ["multer/json"]
2525
[dependencies]
2626
routerify = "1.1"
2727
hyper = "0.13"
28-
multer = "1.0"
28+
multer = "1.1"
2929

3030
[dev-dependencies]
3131
tokio = { version = "0.2", features = ["full"] }

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,67 @@ async fn main() {
8888
}
8989
```
9090

91+
## Prevent DDoS Attack
92+
This crate also provides some APIs to prevent potential `DDoS attack` with fine grained control. It's recommended to add some constraints
93+
on field (specially text field) size to avoid potential `DDoS attack` from attackers running the server out of memory.
94+
95+
An example:
96+
97+
```rust
98+
use hyper::{Body, Request, Response, Server, StatusCode};
99+
use routerify::{Error, Router, RouterService};
100+
// Import `RequestMultipartExt` trait and other types.
101+
use routerify_multipart::{RequestMultipartExt, Constraints, SizeLimit};
102+
use std::net::SocketAddr;
103+
104+
// A handler to handle file uploading in `multipart/form-data` content-type.
105+
async fn file_upload_handler(req: Request<Body>) -> Result<Response<Body>, Error> {
106+
// Create some constraints to be applied to the fields to prevent DDoS attack.
107+
let constraints = Constraints::new()
108+
// We only accept `my_text_field` and `my_file_field` fields,
109+
// For any unknown field, we will throw an error.
110+
.allowed_fields(vec!["my_text_field", "my_file_field"])
111+
.size_limit(
112+
SizeLimit::new()
113+
// Set 15mb as size limit for the whole stream body.
114+
.whole_stream(15 * 1024 * 1024)
115+
// Set 10mb as size limit for all fields.
116+
.per_field(10 * 1024 * 1024)
117+
// Set 30kb as size limit for our text field only.
118+
.for_field("my_text_field", 30 * 1024),
119+
);
120+
121+
// Convert the request into a `Multipart` instance.
122+
let mut multipart = match req.into_multipart_with_constraints(constraints) {
123+
Ok(m) => m,
124+
Err(err) => {
125+
return Ok(Response::builder()
126+
.status(StatusCode::BAD_REQUEST)
127+
.body(Body::from(format!("Bad Request: {}", err)))
128+
.unwrap());
129+
}
130+
};
131+
132+
// Iterate over the fields.
133+
while let Some(mut field) = multipart.next_field().await.map_err(|err| Error::wrap(err))? {
134+
// Get the field name.
135+
let name = field.name();
136+
// Get the field's filename if provided in "Content-Disposition" header.
137+
let file_name = field.file_name();
138+
139+
println!("Name {:?}, File name: {:?}", name, file_name);
140+
141+
// Process the field data chunks e.g. store them in a file.
142+
while let Some(chunk) = field.chunk().await.map_err(|err| Error::wrap(err))? {
143+
// Do something with field chunk.
144+
println!("Chunk: {:?}", chunk);
145+
}
146+
}
147+
148+
Ok(Response::new(Body::from("Success")))
149+
}
150+
```
151+
91152
## Contributing
92153

93154
Your PRs and suggestions are always welcome.

examples/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Examples of using routerify-multipart
2+
3+
These examples show of how to do common tasks using `routerify-multipart`.
4+
5+
Please visit: [Docs](https://docs.rs/routerify-multipart) for the documentation.
6+
7+
Run an example:
8+
9+
```sh
10+
cargo run --example example_name
11+
```
12+
13+
* [`simple_example`](simple_example.rs) - A basic example using `routerify-multipart`.
14+
15+
* [`prevent_ddos_attack`](prevent_ddos_attack.rs) - Shows how to apply some rules to prevent potential `DDoS` attack while handling `multipart/form-data`.

examples/prevent_ddos_attack.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use hyper::{Body, Request, Response, Server, StatusCode};
2+
use routerify::{Error, Router, RouterService};
3+
// Import `RequestMultipartExt` trait and other types.
4+
use routerify_multipart::{Constraints, RequestMultipartExt, SizeLimit};
5+
use std::net::SocketAddr;
6+
7+
// A handler to handle file uploading in `multipart/form-data` content-type.
8+
async fn file_upload_handler(req: Request<Body>) -> Result<Response<Body>, Error> {
9+
// Create some constraints to be applied to the fields to prevent DDoS attack.
10+
let constraints = Constraints::new()
11+
// We only accept `my_text_field` and `my_file_field` fields,
12+
// For any unknown field, we will throw an error.
13+
.allowed_fields(vec!["my_text_field", "my_file_field"])
14+
.size_limit(
15+
SizeLimit::new()
16+
// Set 15mb as size limit for the whole stream body.
17+
.whole_stream(15 * 1024 * 1024)
18+
// Set 10mb as size limit for all fields.
19+
.per_field(10 * 1024 * 1024)
20+
// Set 30kb as size limit for our text field only.
21+
.for_field("my_text_field", 30 * 1024),
22+
);
23+
24+
// Convert the request into a `Multipart` instance.
25+
let mut multipart = match req.into_multipart_with_constraints(constraints) {
26+
Ok(m) => m,
27+
Err(err) => {
28+
return Ok(Response::builder()
29+
.status(StatusCode::BAD_REQUEST)
30+
.body(Body::from(format!("Bad Request: {}", err)))
31+
.unwrap());
32+
}
33+
};
34+
35+
// Iterate over the fields.
36+
while let Some(mut field) = multipart.next_field().await.map_err(|err| Error::wrap(err))? {
37+
// Get the field name.
38+
let name = field.name();
39+
// Get the field's filename if provided in "Content-Disposition" header.
40+
let file_name = field.file_name();
41+
42+
println!("Name {:?}, File name: {:?}", name, file_name);
43+
44+
// Process the field data chunks e.g. store them in a file.
45+
while let Some(chunk) = field.chunk().await.map_err(|err| Error::wrap(err))? {
46+
// Do something with field chunk.
47+
println!("Chunk: {:?}", chunk);
48+
}
49+
}
50+
51+
Ok(Response::new(Body::from("Success")))
52+
}
53+
54+
// Create a router.
55+
fn router() -> Router<Body, Error> {
56+
// Register the handlers.
57+
Router::builder().post("/upload", file_upload_handler).build().unwrap()
58+
}
59+
60+
#[tokio::main]
61+
async fn main() {
62+
let router = router();
63+
64+
// Create a Service from the router above to handle incoming requests.
65+
let service = RouterService::new(router).unwrap();
66+
67+
// The address on which the server will be listening.
68+
let addr = SocketAddr::from(([127, 0, 0, 1], 3001));
69+
70+
// Create a server by passing the created service to `.serve` method.
71+
let server = Server::bind(&addr).serve(service);
72+
73+
println!("App is running on: {}", addr);
74+
if let Err(err) = server.await {
75+
eprintln!("Server error: {}", err);
76+
}
77+
}
File renamed without changes.

src/ext.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::Multipart;
1+
use crate::{Constraints, Multipart};
22
use hyper::{header, Request};
33

44
/// An extension trait which extends [`hyper::Request<Body>`](https://docs.rs/hyper/0.13.5/hyper/struct.Request.html)
@@ -10,6 +10,13 @@ pub trait RequestMultipartExt {
1010
///
1111
/// This method fails if the request body is not `multipart/form-data` and in this case, you could send a `400 Bad Request` status.
1212
fn into_multipart(self) -> routerify::Result<Multipart>;
13+
14+
/// Convert the request body to [`Multipart`](./struct.Multipart.html) if the `content-type` is `multipart/form-data` with some [constraints](./struct.Constraints.html).
15+
///
16+
/// # Errors
17+
///
18+
/// This method fails if the request body is not `multipart/form-data` and in this case, you could send a `400 Bad Request` status.
19+
fn into_multipart_with_constraints(self, constraints: Constraints) -> routerify::Result<Multipart>;
1320
}
1421

1522
impl RequestMultipartExt for Request<hyper::Body> {
@@ -26,4 +33,18 @@ impl RequestMultipartExt for Request<hyper::Body> {
2633
Err(routerify::Error::new("Content-Type is not multipart/form-data"))
2734
}
2835
}
36+
37+
fn into_multipart_with_constraints(self, constraints: Constraints) -> routerify::Result<Multipart> {
38+
let boundary = self
39+
.headers()
40+
.get(header::CONTENT_TYPE)
41+
.and_then(|val| val.to_str().ok())
42+
.and_then(|val| multer::parse_boundary(val).ok());
43+
44+
if let Some(boundary) = boundary {
45+
Ok(Multipart::new_with_constraints(self.into_body(), boundary, constraints))
46+
} else {
47+
Err(routerify::Error::new("Content-Type is not multipart/form-data"))
48+
}
49+
}
2950
}

src/lib.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,72 @@
6868
//! }
6969
//! }
7070
//! ```
71+
//!
72+
//! ## Prevent DDoS Attack
73+
//!
74+
//! This crate also provides some APIs to prevent potential `DDoS attack` with fine grained control. It's recommended to add some constraints
75+
//! on field (specially text field) size to avoid potential `DDoS attack` from attackers running the server out of memory.
76+
//!
77+
//! An example:
78+
//!
79+
//! ```no_run
80+
//! use hyper::{Body, Request, Response, Server, StatusCode};
81+
//! use routerify::{Error, Router, RouterService};
82+
//! // Import `RequestMultipartExt` trait and other types.
83+
//! use routerify_multipart::{RequestMultipartExt, Constraints, SizeLimit};
84+
//! use std::net::SocketAddr;
85+
//!
86+
//! // A handler to handle file uploading in `multipart/form-data` content-type.
87+
//! async fn file_upload_handler(req: Request<Body>) -> Result<Response<Body>, Error> {
88+
//! // Create some constraints to be applied to the fields to prevent DDoS attack.
89+
//! let constraints = Constraints::new()
90+
//! // We only accept `my_text_field` and `my_file_field` fields,
91+
//! // For any unknown field, we will throw an error.
92+
//! .allowed_fields(vec!["my_text_field", "my_file_field"])
93+
//! .size_limit(
94+
//! SizeLimit::new()
95+
//! // Set 15mb as size limit for the whole stream body.
96+
//! .whole_stream(15 * 1024 * 1024)
97+
//! // Set 10mb as size limit for all fields.
98+
//! .per_field(10 * 1024 * 1024)
99+
//! // Set 30kb as size limit for our text field only.
100+
//! .for_field("my_text_field", 30 * 1024),
101+
//! );
102+
//!
103+
//! // Convert the request into a `Multipart` instance.
104+
//! let mut multipart = match req.into_multipart_with_constraints(constraints) {
105+
//! Ok(m) => m,
106+
//! Err(err) => {
107+
//! return Ok(Response::builder()
108+
//! .status(StatusCode::BAD_REQUEST)
109+
//! .body(Body::from(format!("Bad Request: {}", err)))
110+
//! .unwrap());
111+
//! }
112+
//! };
113+
//!
114+
//! // Iterate over the fields.
115+
//! while let Some(mut field) = multipart.next_field().await.map_err(|err| Error::wrap(err))? {
116+
//! // Get the field name.
117+
//! let name = field.name();
118+
//! // Get the field's filename if provided in "Content-Disposition" header.
119+
//! let file_name = field.file_name();
120+
//!
121+
//! println!("Name {:?}, File name: {:?}", name, file_name);
122+
//!
123+
//! // Process the field data chunks e.g. store them in a file.
124+
//! while let Some(chunk) = field.chunk().await.map_err(|err| Error::wrap(err))? {
125+
//! // Do something with field chunk.
126+
//! println!("Chunk: {:?}", chunk);
127+
//! }
128+
//! }
129+
//!
130+
//! Ok(Response::new(Body::from("Success")))
131+
//! }
132+
//! ```
133+
//!
134+
//! Please refer [`Constraints`](./struct.Constraints.html) for more info.
71135
72136
pub use self::ext::RequestMultipartExt;
73-
pub use multer::{Field, Multipart};
137+
pub use multer::{Constraints, Error, Field, Multipart, SizeLimit};
74138

75139
mod ext;

0 commit comments

Comments
 (0)