Skip to main content

ntex_multipart/
extractor.rs

1//! Multipart payload support
2
3use crate::multipart::Multipart;
4use ntex::http::Payload;
5use ntex::web::{ErrorRenderer, FromRequest, HttpRequest};
6use std::convert::Infallible;
7#[cfg(feature = "form")]
8use {
9    crate::form::{Limits, State},
10    crate::multipart_form::MultipartFormConfig,
11    crate::{MultipartCollect, MultipartError, MultipartForm},
12    futures::TryStreamExt,
13    std::collections::HashMap,
14};
15
16/// Get request's payload as multipart stream
17///
18/// Content-type: multipart/form-data;
19///
20/// ## Server example
21///
22/// ```rust
23/// use futures::{Stream, StreamExt};
24/// use ntex::web::{self, HttpResponse, Error};
25/// use ntex_multipart as mp;
26///
27/// async fn index(mut payload: mp::Multipart) -> Result<HttpResponse, Error> {
28///     // iterate over multipart stream
29///     while let Some(item) = payload.next().await {
30///            let mut field = item?;
31///
32///            // Field in turn is stream of *Bytes* object
33///            while let Some(chunk) = field.next().await {
34///                println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk?));
35///            }
36///     }
37///     Ok(HttpResponse::Ok().into())
38/// }
39/// # fn main() {}
40/// ```
41impl<Err> FromRequest<Err> for Multipart
42where
43    Err: ErrorRenderer,
44{
45    type Error = Infallible;
46
47    #[inline]
48    async fn from_request(
49        req: &HttpRequest,
50        payload: &mut Payload,
51    ) -> Result<Self, Self::Error> {
52        Ok(Multipart::new(req.headers(), payload.take()))
53    }
54}
55
56#[cfg(feature = "form")]
57impl<T, Err> FromRequest<Err> for MultipartForm<T>
58where
59    T: MultipartCollect + 'static,
60    Err: ErrorRenderer,
61{
62    type Error = MultipartError;
63
64    #[inline]
65    async fn from_request(
66        req: &HttpRequest,
67        payload: &mut Payload,
68    ) -> Result<Self, Self::Error> {
69        let mut multipart = Multipart::new(req.headers(), payload.take());
70
71        let content_type = match multipart.content_type() {
72            Ok(content_type) => content_type,
73            Err(err) => return Err(err),
74        };
75
76        if content_type.subtype() != mime::FORM_DATA {
77            // this extractor only supports multipart/form-data
78            return Err(MultipartError::IncompatibleContentType);
79        };
80
81        let config = MultipartFormConfig::from_req(req);
82        let mut limits = Limits::new(config.total_limit, config.memory_limit);
83        let mut state = State::default();
84
85        // ensure limits are shared for all fields with this name
86        let mut field_limits = HashMap::<String, Option<usize>>::new();
87
88        while let Some(field) = multipart.try_next().await? {
89            debug_assert!(
90                !field.form_field_name.is_empty(),
91                "multipart form fields should have names",
92            );
93
94            // Retrieve the limit for this field
95            let entry = field_limits
96                .entry(field.form_field_name.clone())
97                .or_insert_with(|| T::limit(&field.form_field_name));
98
99            limits.field_limit_remaining.clone_from(entry);
100
101            T::handle_field(req, field, &mut limits, &mut state).await?;
102
103            // Update the stored limit
104            *entry = limits.field_limit_remaining;
105        }
106
107        let inner = T::from_state(state)?;
108        Ok(MultipartForm(inner))
109    }
110}