Skip to main content

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