-
-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
726 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
use bytes::Bytes; | ||
use futures::stream::Stream; | ||
// Import multer types. | ||
use multer::{Constraints, Multipart, SizeLimit}; | ||
use std::convert::Infallible; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
// Generate a byte stream and the boundary from somewhere e.g. server request body. | ||
let (stream, boundary) = get_byte_stream_from_somewhere().await; | ||
|
||
// Create some constraints to be applied to the fields to prevent DDoS attack. | ||
let constraints = Constraints::new() | ||
// We only accept `my_text_field` and `my_file_field` fields, | ||
// For any unknown field, we will throw an error. | ||
.allowed_fields(vec!["my_text_field", "my_file_field"]) | ||
.size_limit( | ||
SizeLimit::new() | ||
// Set 15mb as size limit for the whole stream body. | ||
.whole_stream(15 * 1024 * 1024) | ||
// Set 10mb as size limit for all fields. | ||
.per_field(10 * 1024 * 1024) | ||
// Set 30kb as size limit for our text field only. | ||
.for_field("my_text_field", 30 * 1024), | ||
); | ||
|
||
// Create a `Multipart` instance from that byte stream and the constraints. | ||
let mut multipart = Multipart::new_with_constraints(stream, boundary, constraints); | ||
|
||
// Iterate over the fields, use `next_field()` to get the next field. | ||
while let Some(field) = multipart.next_field().await? { | ||
// Get field name. | ||
let name = field.name(); | ||
// Get the field's filename if provided in "Content-Disposition" header. | ||
let file_name = field.file_name(); | ||
|
||
println!("Name: {:?}, File Name: {:?}", name, file_name); | ||
|
||
// Read field content as text. | ||
let content = field.text().await?; | ||
println!("Content: {:?}", content); | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
// Generate a byte stream and the boundary from somewhere e.g. server request body. | ||
async fn get_byte_stream_from_somewhere() -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) { | ||
let data = "--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"my_text_field\"\r\n\r\nabcd\r\n--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"my_file_field\"; filename=\"a-text-file.txt\"\r\nContent-Type: text/plain\r\n\r\nHello world\nHello\r\nWorld\rAgain\r\n--X-BOUNDARY--\r\n"; | ||
let stream = futures::stream::iter( | ||
data.chars() | ||
.map(|ch| ch.to_string()) | ||
.map(|part| Ok(Bytes::copy_from_slice(part.as_bytes()))), | ||
); | ||
|
||
(stream, "X-BOUNDARY") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
use crate::size_limit::SizeLimit; | ||
|
||
/// Represents some rules to be applied on the stream and field's content size to prevent `DDoS attack`. | ||
/// | ||
/// It's recommended to add some rules on field (specially text field) size to avoid potential `DDoS attack` from attackers running the server out of memory. | ||
/// This type provides some API to apply constraints on very granular level to make `multipart/form-data` safe. | ||
/// By default, it does not apply any constraint. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use multer::{Multipart, Constraints, SizeLimit}; | ||
/// # use bytes::Bytes; | ||
/// # use std::convert::Infallible; | ||
/// # use futures::stream::once; | ||
/// | ||
/// # async fn run() { | ||
/// # let data = "--X-BOUNDARY\r\nContent-Disposition: form-data; name=\"my_text_field\"\r\n\r\nabcd\r\n--X-BOUNDARY--\r\n"; | ||
/// # let some_stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) }); | ||
/// // Create some constraints to be applied to the fields to prevent DDoS attack. | ||
/// let constraints = Constraints::new() | ||
/// // We only accept `my_text_field` and `my_file_field` fields, | ||
/// // For any unknown field, we will throw an error. | ||
/// .allowed_fields(vec!["my_text_field", "my_file_field"]) | ||
/// .size_limit( | ||
/// SizeLimit::new() | ||
/// // Set 15mb as size limit for the whole stream body. | ||
/// .whole_stream(15 * 1024 * 1024) | ||
/// // Set 10mb as size limit for all fields. | ||
/// .per_field(10 * 1024 * 1024) | ||
/// // Set 30kb as size limit for our text field only. | ||
/// .for_field("my_text_field", 30 * 1024), | ||
/// ); | ||
/// | ||
/// // Create a `Multipart` instance from a stream and the constraints. | ||
/// let mut multipart = Multipart::new_with_constraints(some_stream, "X-BOUNDARY", constraints); | ||
/// | ||
/// while let Some(field) = multipart.next_field().await.unwrap() { | ||
/// let content = field.text().await.unwrap(); | ||
/// assert_eq!(content, "abcd"); | ||
/// } | ||
/// # } | ||
/// # tokio::runtime::Runtime::new().unwrap().block_on(run()); | ||
/// ``` | ||
pub struct Constraints { | ||
pub(crate) size_limit: SizeLimit, | ||
pub(crate) allowed_fields: Option<Vec<String>>, | ||
} | ||
|
||
impl Constraints { | ||
/// Creates a set of rules with default behaviour. | ||
pub fn new() -> Constraints { | ||
Constraints::default() | ||
} | ||
|
||
/// Applies rules on field's content length. | ||
pub fn size_limit(self, size_limit: SizeLimit) -> Constraints { | ||
Constraints { | ||
size_limit, | ||
allowed_fields: self.allowed_fields, | ||
} | ||
} | ||
|
||
/// Specify which fields should be allowed, for any unknown field, the [`next_field`](./struct.Multipart.html#method.next_field) will throw an error. | ||
pub fn allowed_fields<N: Into<String>>(self, allowed_fields: Vec<N>) -> Constraints { | ||
let allowed_fields = allowed_fields.into_iter().map(|item| item.into()).collect(); | ||
|
||
Constraints { | ||
size_limit: self.size_limit, | ||
allowed_fields: Some(allowed_fields), | ||
} | ||
} | ||
|
||
pub(crate) fn is_it_allowed(&self, field: Option<&str>) -> bool { | ||
if let Some(ref allowed_fields) = self.allowed_fields { | ||
field | ||
.map(|field| allowed_fields.iter().any(|item| item == field)) | ||
.unwrap_or(false) | ||
} else { | ||
true | ||
} | ||
} | ||
} | ||
|
||
impl Default for Constraints { | ||
fn default() -> Self { | ||
Constraints { | ||
size_limit: SizeLimit::default(), | ||
allowed_fields: None, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
use crate::constants; | ||
use http::header::{self, HeaderMap}; | ||
|
||
pub(crate) struct ContentDisposition { | ||
pub(crate) field_name: Option<String>, | ||
pub(crate) file_name: Option<String>, | ||
} | ||
|
||
impl ContentDisposition { | ||
pub fn parse(headers: &HeaderMap) -> ContentDisposition { | ||
let content_disposition = headers | ||
.get(header::CONTENT_DISPOSITION) | ||
.and_then(|val| val.to_str().ok()); | ||
|
||
let field_name = content_disposition | ||
.and_then(|val| constants::CONTENT_DISPOSITION_FIELD_NAME_RE.captures(val)) | ||
.and_then(|cap| cap.get(1)) | ||
.map(|m| m.as_str().to_owned()); | ||
|
||
let file_name = content_disposition | ||
.and_then(|val| constants::CONTENT_DISPOSITION_FILE_NAME_RE.captures(val)) | ||
.and_then(|cap| cap.get(1)) | ||
.map(|m| m.as_str().to_owned()); | ||
|
||
ContentDisposition { field_name, file_name } | ||
} | ||
} |
Oops, something went wrong.