Skip to main content

warp/filters/
multipart.rs

1//! Multipart body filters
2//!
3//! [`Filter`](crate::Filter)s that extract a multipart body for a route.
4
5use std::error::Error as StdError;
6use std::fmt::{Display, Formatter};
7use std::future::Future;
8use std::pin::Pin;
9use std::task::{Context, Poll};
10use std::{fmt, io};
11
12use bytes::{Buf, Bytes};
13use futures_util::{future, Stream};
14use headers::ContentType;
15use http_body_util::BodyDataStream;
16use mime::Mime;
17use multer::{Field as PartInner, Multipart as FormDataInner};
18
19use crate::bodyt::Body;
20use crate::filter::{Filter, FilterBase, Internal};
21use crate::reject::{self, Rejection};
22
23// If not otherwise configured, default to 2MB.
24const DEFAULT_FORM_DATA_MAX_LENGTH: u64 = 1024 * 1024 * 2;
25
26/// A [`Filter`](crate::Filter) to extract a `multipart/form-data` body from a request.
27///
28/// Create with the `warp::multipart::form()` function.
29#[derive(Debug, Clone)]
30pub struct FormOptions {
31    max_length: Option<u64>,
32}
33
34/// A `Stream` of multipart/form-data `Part`s.
35///
36/// Extracted with a `warp::multipart::form` filter.
37pub struct FormData {
38    inner: FormDataInner<'static>,
39}
40
41/// A single "part" of a multipart/form-data body.
42///
43/// Yielded from the `FormData` stream.
44pub struct Part {
45    part: PartInner<'static>,
46}
47
48/// Create a [`Filter`](crate::Filter) to extract a `multipart/form-data` body from a request.
49///
50/// The extracted `FormData` type is a `Stream` of `Part`s, and each `Part`
51/// in turn is a `Stream` of bytes.
52pub fn form() -> FormOptions {
53    FormOptions {
54        max_length: Some(DEFAULT_FORM_DATA_MAX_LENGTH),
55    }
56}
57
58// ===== impl Form =====
59
60impl FormOptions {
61    /// Set the maximum byte length allowed for this body.
62    ///
63    /// `max_length(None)` means that maximum byte length is not checked.
64    /// Defaults to 2MB.
65    pub fn max_length(mut self, max: impl Into<Option<u64>>) -> Self {
66        self.max_length = max.into();
67        self
68    }
69}
70
71type FormFut = Pin<Box<dyn Future<Output = Result<(FormData,), Rejection>> + Send>>;
72
73impl FilterBase for FormOptions {
74    type Extract = (FormData,);
75    type Error = Rejection;
76    type Future = FormFut;
77
78    fn filter(&self, _: Internal) -> Self::Future {
79        let boundary = super::header::header2::<ContentType>().and_then(|ct| {
80            let mime = Mime::from(ct);
81            let mime = mime
82                .get_param("boundary")
83                .map(|v| v.to_string())
84                .ok_or_else(|| reject::invalid_header("content-type"));
85            future::ready(mime)
86        });
87
88        let filt = boundary
89            .and(super::body::body())
90            .map(|boundary: String, body| {
91                let body = BodyIoError(BodyDataStream::new(body));
92                FormData {
93                    inner: FormDataInner::new(body, &boundary),
94                }
95            });
96
97        if let Some(max_length) = self.max_length {
98            Box::pin(
99                super::body::content_length_limit(max_length)
100                    .and(filt)
101                    .filter(Internal),
102            )
103        } else {
104            Box::pin(filt.filter(Internal))
105        }
106    }
107}
108
109// ===== impl FormData =====
110
111impl fmt::Debug for FormData {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        f.debug_struct("FormData").finish()
114    }
115}
116
117impl Stream for FormData {
118    type Item = Result<Part, crate::Error>;
119
120    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
121        match self.inner.poll_next_field(cx) {
122            Poll::Pending => Poll::Pending,
123            Poll::Ready(Ok(Some(part))) => {
124                if part.name().is_some() || part.file_name().is_some() {
125                    Poll::Ready(Some(Ok(Part { part })))
126                } else {
127                    Poll::Ready(Some(Err(crate::Error::new(MultipartFieldMissingName))))
128                }
129            }
130            Poll::Ready(Ok(None)) => Poll::Ready(None),
131            Poll::Ready(Err(err)) => Poll::Ready(Some(Err(crate::Error::new(err)))),
132        }
133    }
134}
135
136// ===== impl Part =====
137
138impl Part {
139    /// Get the name of this part.
140    pub fn name(&self) -> &str {
141        self.part
142            .name()
143            .unwrap_or_else(|| self.part.file_name().expect("checked for name previously"))
144    }
145
146    /// Get the filename of this part, if present.
147    pub fn filename(&self) -> Option<&str> {
148        self.part.file_name()
149    }
150
151    /// Get the content-type of this part, if present.
152    pub fn content_type(&self) -> Option<&str> {
153        let content_type = self.part.content_type();
154        content_type.map(|t| t.as_ref())
155    }
156
157    /// Asynchronously get some of the data for this `Part`.
158    pub async fn data(&mut self) -> Option<Result<impl Buf, crate::Error>> {
159        future::poll_fn(|cx| self.poll_next(cx)).await
160    }
161
162    /// Convert this `Part` into a `Stream` of `Buf`s.
163    pub fn stream(self) -> impl Stream<Item = Result<impl Buf, crate::Error>> {
164        PartStream(self)
165    }
166
167    fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, crate::Error>>> {
168        match Pin::new(&mut self.part).poll_next(cx) {
169            Poll::Pending => Poll::Pending,
170            Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))),
171            Poll::Ready(None) => Poll::Ready(None),
172            Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(crate::Error::new(err)))),
173        }
174    }
175}
176
177impl fmt::Debug for Part {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        let mut builder = f.debug_struct("Part");
180        builder.field("name", &self.name());
181
182        if let Some(ref filename) = self.part.file_name() {
183            builder.field("filename", filename);
184        }
185
186        if let Some(ref mime) = self.part.content_type() {
187            builder.field("content_type", mime);
188        }
189
190        builder.finish()
191    }
192}
193
194struct PartStream(Part);
195
196impl Stream for PartStream {
197    type Item = Result<Bytes, crate::Error>;
198
199    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
200        self.0.poll_next(cx)
201    }
202}
203
204struct BodyIoError(BodyDataStream<Body>);
205
206impl Stream for BodyIoError {
207    type Item = io::Result<Bytes>;
208
209    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
210        match Pin::new(&mut self.0).poll_next(cx) {
211            Poll::Pending => Poll::Pending,
212            Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(bytes))),
213            Poll::Ready(None) => Poll::Ready(None),
214            Poll::Ready(Some(Err(err))) => {
215                Poll::Ready(Some(Err(io::Error::new(io::ErrorKind::Other, err))))
216            }
217        }
218    }
219}
220
221/// An error used when a multipart field is missing a name.
222#[derive(Debug)]
223struct MultipartFieldMissingName;
224
225impl Display for MultipartFieldMissingName {
226    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
227        write!(f, "Multipart field is missing a name")
228    }
229}
230
231impl StdError for MultipartFieldMissingName {}