multer/lib.rs
1//! An async parser for `multipart/form-data` content-type in Rust.
2//!
3//! It accepts a [`Stream`](futures_util::stream::Stream) of
4//! [`Bytes`](bytes::Bytes), or with the `tokio-io` feature enabled, an
5//! `AsyncRead` reader as a source, so that it can be plugged into any async
6//! Rust environment e.g. any async server.
7//!
8//! To enable trace logging via the `log` crate, enable the `log` feature.
9//!
10//! # Examples
11//!
12//! ```no_run
13//! use std::convert::Infallible;
14//!
15//! use bytes::Bytes;
16//! // Import multer types.
17//! use futures_util::stream::once;
18//! use futures_util::stream::Stream;
19//! use multer::Multipart;
20//!
21//! #[tokio::main]
22//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
23//! // Generate a byte stream and the boundary from somewhere e.g. server request body.
24//! let (stream, boundary) = get_byte_stream_from_somewhere().await;
25//!
26//! // Create a `Multipart` instance from that byte stream and the boundary.
27//! let mut multipart = Multipart::new(stream, boundary);
28//!
29//! // Iterate over the fields, use `next_field()` to get the next field.
30//! while let Some(mut field) = multipart.next_field().await? {
31//! // Get field name.
32//! let name = field.name();
33//! // Get the field's filename if provided in "Content-Disposition" header.
34//! let file_name = field.file_name();
35//!
36//! println!("Name: {:?}, File Name: {:?}", name, file_name);
37//!
38//! // Process the field data chunks e.g. store them in a file.
39//! while let Some(chunk) = field.chunk().await? {
40//! // Do something with field chunk.
41//! println!("Chunk: {:?}", chunk);
42//! }
43//! }
44//!
45//! Ok(())
46//! }
47//!
48//! // Generate a byte stream and the boundary from somewhere e.g. server request body.
49//! async fn get_byte_stream_from_somewhere(
50//! ) -> (impl Stream<Item = Result<Bytes, Infallible>>, &'static str) {
51//! let data = "--X-BOUNDARY\r\nContent-Disposition: form-data; \
52//! name=\"my_text_field\"\r\n\r\nabcd\r\n--X-BOUNDARY--\r\n";
53//!
54//! let stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });
55//! (stream, "X-BOUNDARY")
56//! }
57//! ```
58//!
59//! ## Prevent Denial of Service (DoS) Attack
60//!
61//! This crate also provides some APIs to prevent potential DoS attacks with
62//! fine grained control. It's recommended to add some constraints
63//! on field (specially text field) size to avoid potential DoS attacks from
64//! attackers running the server out of memory.
65//!
66//! An example:
67//!
68//! ```
69//! use multer::{Constraints, Multipart, SizeLimit};
70//! # use bytes::Bytes;
71//! # use std::convert::Infallible;
72//! # use futures_util::stream::once;
73//!
74//! # async fn run() {
75//! # let data = "--X-BOUNDARY\r\nContent-Disposition: form-data; \
76//! # name=\"my_text_field\"\r\n\r\nabcd\r\n--X-BOUNDARY--\r\n";
77//! # let some_stream = once(async move { Result::<Bytes, Infallible>::Ok(Bytes::from(data)) });
78//! // Create some constraints to be applied to the fields to prevent DoS attack.
79//! let constraints = Constraints::new()
80//! // We only accept `my_text_field` and `my_file_field` fields,
81//! // For any unknown field, we will throw an error.
82//! .allowed_fields(vec!["my_text_field", "my_file_field"])
83//! .size_limit(
84//! SizeLimit::new()
85//! // Set 15mb as size limit for the whole stream body.
86//! .whole_stream(15 * 1024 * 1024)
87//! // Set 10mb as size limit for all fields.
88//! .per_field(10 * 1024 * 1024)
89//! // Set 30kb as size limit for our text field only.
90//! .for_field("my_text_field", 30 * 1024),
91//! );
92//!
93//! // Create a `Multipart` instance from a stream and the constraints.
94//! let mut multipart = Multipart::with_constraints(some_stream, "X-BOUNDARY", constraints);
95//!
96//! while let Some(field) = multipart.next_field().await.unwrap() {
97//! let content = field.text().await.unwrap();
98//! assert_eq!(content, "abcd");
99//! }
100//! # }
101//! # tokio::runtime::Runtime::new().unwrap().block_on(run());
102//! ```
103//!
104//! Please refer [`Constraints`] for more info.
105//!
106//! ## Usage with [hyper.rs](https://hyper.rs/) server
107//!
108//! An [example](https://github.com/rousan/multer-rs/blob/master/examples/hyper_server_example.rs) showing usage with [hyper.rs](https://hyper.rs/).
109//!
110//! For more examples, please visit [examples](https://github.com/rousan/multer-rs/tree/master/examples).
111
112#![forbid(unsafe_code)]
113#![warn(
114 missing_debug_implementations,
115 rust_2018_idioms,
116 trivial_casts,
117 unused_qualifications
118)]
119#![cfg_attr(nightly, feature(doc_cfg))]
120#![doc(test(attr(deny(rust_2018_idioms, warnings))))]
121#![doc(test(attr(allow(unused_extern_crates, unused_variables))))]
122
123pub use bytes;
124pub use constraints::Constraints;
125pub use error::Error;
126pub use field::Field;
127pub use multipart::Multipart;
128pub use size_limit::SizeLimit;
129
130#[cfg(feature = "log")]
131macro_rules! trace {
132 ($($t:tt)*) => (::log::trace!($($t)*););
133}
134
135#[cfg(not(feature = "log"))]
136macro_rules! trace {
137 ($($t:tt)*) => {};
138}
139
140mod buffer;
141mod constants;
142mod constraints;
143mod content_disposition;
144mod error;
145mod field;
146mod helpers;
147mod multipart;
148mod size_limit;
149
150/// A Result type often returned from methods that can have `multer` errors.
151pub type Result<T, E = Error> = std::result::Result<T, E>;
152
153/// Parses the `Content-Type` header to extract the boundary value.
154///
155/// # Examples
156///
157/// ```
158/// # fn run(){
159/// let content_type = "multipart/form-data; boundary=ABCDEFG";
160///
161/// assert_eq!(
162/// multer::parse_boundary(content_type),
163/// Ok("ABCDEFG".to_owned())
164/// );
165/// # }
166/// # run();
167/// ```
168pub fn parse_boundary<T: AsRef<str>>(content_type: T) -> Result<String> {
169 let m = content_type
170 .as_ref()
171 .parse::<mime::Mime>()
172 .map_err(Error::DecodeContentType)?;
173
174 if !(m.type_() == mime::MULTIPART && m.subtype() == mime::FORM_DATA) {
175 return Err(Error::NoMultipart);
176 }
177
178 m.get_param(mime::BOUNDARY)
179 .map(|name| name.as_str().to_owned())
180 .ok_or(Error::NoBoundary)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_parse_boundary() {
189 let content_type = "multipart/form-data; boundary=ABCDEFG";
190 assert_eq!(parse_boundary(content_type), Ok("ABCDEFG".to_owned()));
191
192 let content_type = "multipart/form-data; boundary=------ABCDEFG";
193 assert_eq!(parse_boundary(content_type), Ok("------ABCDEFG".to_owned()));
194
195 let content_type = "boundary=------ABCDEFG";
196 assert!(parse_boundary(content_type).is_err());
197
198 let content_type = "text/plain";
199 assert!(parse_boundary(content_type).is_err());
200
201 let content_type = "text/plain; boundary=------ABCDEFG";
202 assert!(parse_boundary(content_type).is_err());
203 }
204}