actix_easy_multipart/
lib.rs

1//! Typed multipart form extractor for actix-web.
2#![allow(clippy::type_complexity)]
3pub mod bytes;
4pub mod json;
5#[cfg(feature = "tempfile")]
6pub mod tempfile;
7pub mod text;
8
9use actix_http::error::PayloadError;
10use actix_multipart::{Field, Multipart, MultipartError};
11use actix_web::dev::Payload;
12use actix_web::http::{header, StatusCode};
13use actix_web::{web, FromRequest, HttpRequest, ResponseError};
14use derive_more::{Deref, DerefMut, Display, Error, From};
15use futures_core::future::LocalBoxFuture;
16use futures_util::TryFutureExt;
17use futures_util::{FutureExt, TryStreamExt};
18use std::any::Any;
19use std::collections::HashMap;
20use std::future::{ready, Future};
21use std::sync::Arc;
22
23// This allows us to use the actix_multipart_derive within this crate's tests
24#[cfg(test)]
25extern crate self as actix_easy_multipart;
26
27// Re-export actix-multipart for use in macro
28#[doc(hidden)]
29pub use actix_multipart;
30
31/// Implements the [`MultipartFormTrait`] for a struct so that it can be used with the
32/// [`struct@MultipartForm`] extractor.
33///
34/// ## Simple Example
35///
36/// Each field type should implement the [`FieldReader`] trait:
37///
38/// ```
39/// # #[cfg(feature = "tempfile")] {
40/// # use actix_easy_multipart::tempfile::Tempfile;
41/// # use actix_easy_multipart::text::Text;
42/// # use actix_easy_multipart::MultipartForm;
43/// #[derive(MultipartForm)]
44/// struct ImageUpload {
45///     description: Text<String>,
46///     timestamp: Text<i64>,
47///     image: Tempfile,
48/// }
49/// # }
50/// ```
51///
52/// ## Optional and List Fields
53///
54/// You can also use `Vec<T>` and `Option<T>` provided that `T: FieldReader`.
55///
56/// A [`Vec`] field corresponds to an upload with multiple parts under the
57/// [same field name](https://www.rfc-editor.org/rfc/rfc7578#section-4.3).
58///
59/// ```
60/// # #[cfg(feature = "tempfile")] {
61/// # use actix_easy_multipart::tempfile::Tempfile;
62/// # use actix_easy_multipart::text::Text;
63/// # use actix_easy_multipart::MultipartForm;
64/// #[derive(MultipartForm)]
65/// struct Form {
66///     category: Option<Text<String>>,
67///     files: Vec<Tempfile>,
68/// }
69/// # }
70/// ```
71///
72/// ## Field Renaming
73///
74/// You can use the `#[multipart(rename="")]` attribute to receive a field by a different name.
75///
76/// ```
77/// # #[cfg(feature = "tempfile")] {
78/// # use actix_easy_multipart::tempfile::Tempfile;
79/// # use actix_easy_multipart::MultipartForm;
80/// #[derive(MultipartForm)]
81/// struct Form {
82///     #[multipart(rename="files[]")]
83///     files: Vec<Tempfile>,
84/// }
85/// # }
86/// ```
87///
88/// ## Field Limits
89///
90/// You can use the `#[multipart(limit="")]` attribute to set field level limits. The limit
91/// string is parsed using [parse_size](https://docs.rs/parse-size/1.0.0/parse_size/).
92///
93/// Note: the form is also subject to the global limits configured using the
94/// [`MultipartFormConfig`].
95///
96/// ```
97/// # #[cfg(feature = "tempfile")] {
98/// # use actix_easy_multipart::tempfile::Tempfile;
99/// # use actix_easy_multipart::text::Text;
100/// # use actix_easy_multipart::MultipartForm;
101/// #[derive(MultipartForm)]
102/// struct Form {
103///     #[multipart(limit="2KiB")]
104///     description: Text<String>,
105///     #[multipart(limit="512MiB")]
106///     files: Vec<Tempfile>,
107/// }
108/// # }
109/// ```
110///
111/// ## Unknown Fields
112///
113/// By default fields with an unknown name are ignored. You can change this using the
114/// `#[multipart(deny_unknown_fields)]` attribute:
115///
116/// ```
117/// # use actix_easy_multipart::MultipartForm;
118/// #[derive(MultipartForm)]
119/// #[multipart(deny_unknown_fields)]
120/// struct Form { }
121/// ```
122///
123/// ## Duplicate Fields
124///
125/// You can change the behaviour for when multiple fields are received with the same name using the
126/// `#[multipart(duplicate_action = "")]` attribute:
127///
128/// - "ignore": Extra fields are ignored (default).
129/// - "replace": Each field is processed, but only the last one is persisted.
130/// - "deny": An [Error::UnsupportedField] error is returned.
131///
132/// (Note this option does not apply to `Vec` fields)
133///
134/// ```
135/// # use actix_easy_multipart::MultipartForm;
136/// #[derive(MultipartForm)]
137/// #[multipart(duplicate_action = "deny")]
138/// struct Form { }
139/// ```
140pub use actix_easy_multipart_derive::MultipartForm;
141
142#[derive(Debug, Display, Error, From)]
143pub enum Error {
144    #[display(fmt = "{}", _0)]
145    Multipart(actix_multipart::MultipartError),
146
147    /// An error from a field handler in a form
148    #[display(fmt = "An error occurred processing field `{field_name}`: {source}")]
149    Field {
150        field_name: String,
151        source: actix_web::Error,
152    },
153
154    /// Duplicate field
155    #[display(fmt = "Duplicate field found for: `{}`", _0)]
156    #[from(ignore)]
157    DuplicateField(#[error(not(source))] String),
158
159    /// Missing field
160    #[display(fmt = "Field with name `{}` is required", _0)]
161    #[from(ignore)]
162    MissingField(#[error(not(source))] String),
163
164    /// Unknown field
165    #[display(fmt = "Unsupported field `{}`", _0)]
166    #[from(ignore)]
167    UnsupportedField(#[error(not(source))] String),
168}
169
170impl ResponseError for Error {
171    fn status_code(&self) -> StatusCode {
172        match &self {
173            Error::Field { source, .. } => source.as_response_error().status_code(),
174            _ => StatusCode::BAD_REQUEST,
175        }
176    }
177}
178
179/// Trait that data types to be used in a multipart form struct should implement.
180///
181/// It represents an asynchronous handler that processes a multipart field to produce `Self`.
182pub trait FieldReader<'t>: Sized + Any {
183    /// Future that resolves to a `Self`.
184    type Future: Future<Output = Result<Self, Error>>;
185
186    /// The form will call this function to handle the field.
187    fn read_field(req: &'t HttpRequest, field: Field, limits: &'t mut Limits) -> Self::Future;
188}
189
190/// Used to accumulate the state of the loaded fields.
191#[doc(hidden)]
192#[derive(Default, Deref, DerefMut)]
193pub struct State(pub HashMap<String, Box<dyn Any>>);
194
195// Trait that the field collection types implement, i.e. `Vec<T>`, `Option<T>`, or `T` itself.
196#[doc(hidden)]
197pub trait FieldGroupReader<'t>: Sized + Any {
198    type Future: Future<Output = Result<(), Error>>;
199
200    /// The form will call this function for each matching field
201    fn handle_field(
202        req: &'t HttpRequest,
203        field: Field,
204        limits: &'t mut Limits,
205        state: &'t mut State,
206        duplicate_action: DuplicateAction,
207    ) -> Self::Future;
208
209    /// Create `Self` from the group of processed fields
210    fn from_state(name: &str, state: &'t mut State) -> Result<Self, Error>;
211}
212
213impl<'t, T> FieldGroupReader<'t> for Option<T>
214where
215    T: FieldReader<'t>,
216{
217    type Future = LocalBoxFuture<'t, Result<(), Error>>;
218
219    fn handle_field(
220        req: &'t HttpRequest,
221        field: Field,
222        limits: &'t mut Limits,
223        state: &'t mut State,
224        duplicate_action: DuplicateAction,
225    ) -> Self::Future {
226        if state.contains_key(field.name()) {
227            match duplicate_action {
228                DuplicateAction::Ignore => return ready(Ok(())).boxed_local(),
229                DuplicateAction::Deny => {
230                    return ready(Err(Error::DuplicateField(field.name().to_string())))
231                        .boxed_local()
232                }
233                DuplicateAction::Replace => {}
234            }
235        }
236        async move {
237            let field_name = field.name().to_string();
238            let t = T::read_field(req, field, limits).await?;
239            state.insert(field_name, Box::new(t));
240            Ok(())
241        }
242        .boxed_local()
243    }
244
245    fn from_state(name: &str, state: &'t mut State) -> Result<Self, Error> {
246        Ok(state.remove(name).map(|m| *m.downcast::<T>().unwrap()))
247    }
248}
249
250impl<'t, T> FieldGroupReader<'t> for Vec<T>
251where
252    T: FieldReader<'t>,
253{
254    type Future = LocalBoxFuture<'t, Result<(), Error>>;
255
256    fn handle_field(
257        req: &'t HttpRequest,
258        field: Field,
259        limits: &'t mut Limits,
260        state: &'t mut State,
261        _duplicate_action: DuplicateAction,
262    ) -> Self::Future {
263        // Vec GroupReader always allows duplicates!
264        async move {
265            let field_name = field.name().to_string();
266            let vec = state
267                .entry(field_name)
268                .or_insert_with(|| Box::new(Vec::<T>::new()))
269                .downcast_mut::<Vec<T>>()
270                .unwrap();
271            let item = T::read_field(req, field, limits).await?;
272            vec.push(item);
273            Ok(())
274        }
275        .boxed_local()
276    }
277
278    fn from_state(name: &str, state: &'t mut State) -> Result<Self, Error> {
279        Ok(state
280            .remove(name)
281            .map(|m| *m.downcast::<Vec<T>>().unwrap())
282            .unwrap_or_default())
283    }
284}
285
286impl<'t, T> FieldGroupReader<'t> for T
287where
288    T: FieldReader<'t>,
289{
290    type Future = LocalBoxFuture<'t, Result<(), Error>>;
291
292    fn handle_field(
293        req: &'t HttpRequest,
294        field: Field,
295        limits: &'t mut Limits,
296        state: &'t mut State,
297        duplicate_action: DuplicateAction,
298    ) -> Self::Future {
299        if state.contains_key(field.name()) {
300            match duplicate_action {
301                DuplicateAction::Ignore => return ready(Ok(())).boxed_local(),
302                DuplicateAction::Deny => {
303                    return ready(Err(Error::DuplicateField(field.name().to_string())))
304                        .boxed_local()
305                }
306                DuplicateAction::Replace => {}
307            }
308        }
309        async move {
310            let field_name = field.name().to_string();
311            let t = T::read_field(req, field, limits).await?;
312            state.insert(field_name, Box::new(t));
313            Ok(())
314        }
315        .boxed_local()
316    }
317
318    fn from_state(name: &str, state: &'t mut State) -> Result<Self, Error> {
319        state
320            .remove(name)
321            .map(|m| *m.downcast::<T>().unwrap())
322            .ok_or_else(|| Error::MissingField(name.to_owned()))
323    }
324}
325
326/// Trait that allows a type to be used in the [`struct@MultipartForm`] extractor. You should use
327/// the [`macro@MultipartForm`] to implement this for your struct.
328pub trait MultipartFormTrait: Sized {
329    /// An optional limit in bytes to be applied a given field name. Note this limit will be shared
330    /// across all fields sharing the same name.
331    fn limit(field_name: &str) -> Option<usize>;
332
333    /// The extractor will call this function for each incoming field, the state can be updated
334    /// with the processed field data.
335    fn handle_field<'t>(
336        req: &'t HttpRequest,
337        field: Field,
338        limits: &'t mut Limits,
339        state: &'t mut State,
340    ) -> LocalBoxFuture<'t, Result<(), Error>>;
341
342    /// Once all the fields have been processed and stored in the state, this is called
343    /// to convert into the struct representation.
344    fn from_state(state: State) -> Result<Self, Error>;
345}
346
347#[doc(hidden)]
348pub enum DuplicateAction {
349    /// Additional fields are not processed
350    Ignore,
351    /// An error will be raised
352    Deny,
353    /// All fields will be processed, the last one will replace all previous
354    Replace,
355}
356
357/// Used to keep track of the remaining limits for the form and current field.
358pub struct Limits {
359    pub total_limit_remaining: usize,
360    pub memory_limit_remaining: usize,
361    pub field_limit_remaining: Option<usize>,
362}
363
364impl Limits {
365    pub fn new(total_limit: usize, memory_limit: usize) -> Self {
366        Self {
367            total_limit_remaining: total_limit,
368            memory_limit_remaining: memory_limit,
369            field_limit_remaining: None,
370        }
371    }
372
373    /// This function should be called within a [`FieldReader`] when reading each chunk of a field
374    /// to ensure that the form limits are not exceeded.
375    ///
376    /// # Arguments
377    ///
378    /// * `bytes` - The number of bytes being read from this chunk
379    /// * `in_memory` - Whether to consume from the memory limits
380    pub fn try_consume_limits(&mut self, bytes: usize, in_memory: bool) -> Result<(), Error> {
381        self.total_limit_remaining = self
382            .total_limit_remaining
383            .checked_sub(bytes)
384            .ok_or(MultipartError::Payload(PayloadError::Overflow))?;
385        if in_memory {
386            self.memory_limit_remaining = self
387                .memory_limit_remaining
388                .checked_sub(bytes)
389                .ok_or(MultipartError::Payload(PayloadError::Overflow))?;
390        }
391        if let Some(field_limit) = self.field_limit_remaining {
392            self.field_limit_remaining = Some(
393                field_limit
394                    .checked_sub(bytes)
395                    .ok_or(MultipartError::Payload(PayloadError::Overflow))?,
396            );
397        }
398        Ok(())
399    }
400}
401
402/// Typed `multipart/form-data` extractor.
403///
404/// To extract typed data from a multipart stream, the inner type `T` must implement the
405/// [`MultipartFormTrait`] trait, you should use the [`macro@MultipartForm`] macro to derive this for
406/// your struct.
407///
408/// Use [`MultipartFormConfig`] to configure extraction options.
409#[derive(Deref, DerefMut)]
410pub struct MultipartForm<T: MultipartFormTrait>(pub T);
411
412impl<T: MultipartFormTrait> MultipartForm<T> {
413    /// Unwrap into inner `T` value.
414    pub fn into_inner(self) -> T {
415        self.0
416    }
417}
418
419impl<T> FromRequest for MultipartForm<T>
420where
421    T: MultipartFormTrait,
422{
423    type Error = actix_web::Error;
424    type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
425
426    #[inline]
427    fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
428        let mut payload = Multipart::new(req.headers(), payload.take());
429        let config = MultipartFormConfig::from_req(req);
430        let mut limits = Limits::new(config.total_limit, config.memory_limit);
431        let req = req.clone();
432        let req2 = req.clone();
433        let err_handler = config.err_handler.clone();
434
435        async move {
436            let mut state = State::default();
437            // We need to ensure field limits are shared for all instances of this field name
438            let mut field_limits = HashMap::<String, Option<usize>>::new();
439
440            while let Some(field) = payload.try_next().await? {
441                // Retrieve the limit for this field
442                let entry = field_limits
443                    .entry(field.name().to_owned())
444                    .or_insert_with(|| T::limit(field.name()));
445                limits.field_limit_remaining = entry.to_owned();
446
447                T::handle_field(&req, field, &mut limits, &mut state).await?;
448
449                // Update the stored limit
450                *entry = limits.field_limit_remaining;
451            }
452            let inner = T::from_state(state)?;
453            Ok(MultipartForm(inner))
454        }
455        .map_err(move |e| {
456            if let Some(handler) = err_handler {
457                (*handler)(e, &req2)
458            } else {
459                e.into()
460            }
461        })
462        .boxed_local()
463    }
464}
465
466type MultipartFormErrorHandler =
467    Option<Arc<dyn Fn(Error, &HttpRequest) -> actix_web::Error + Send + Sync>>;
468
469/// [`struct@MultipartForm`] extractor configuration.
470#[derive(Clone)]
471pub struct MultipartFormConfig {
472    total_limit: usize,
473    memory_limit: usize,
474    err_handler: MultipartFormErrorHandler,
475}
476
477impl MultipartFormConfig {
478    /// Set maximum accepted payload size for the entire form. By default this limit is 50MiB.
479    pub fn total_limit(mut self, total_limit: usize) -> Self {
480        self.total_limit = total_limit;
481        self
482    }
483
484    /// Set maximum accepted data that will be read into memory. By default this limit is 2MiB.
485    pub fn memory_limit(mut self, memory_limit: usize) -> Self {
486        self.memory_limit = memory_limit;
487        self
488    }
489
490    /// Set custom error handler.
491    pub fn error_handler<F>(mut self, f: F) -> Self
492    where
493        F: Fn(Error, &HttpRequest) -> actix_web::Error + Send + Sync + 'static,
494    {
495        self.err_handler = Some(Arc::new(f));
496        self
497    }
498
499    /// Extract payload config from app data. Check both `T` and `Data<T>`, in that order, and fall
500    /// back to the default payload config.
501    fn from_req(req: &HttpRequest) -> &Self {
502        req.app_data::<Self>()
503            .or_else(|| req.app_data::<web::Data<Self>>().map(|d| d.as_ref()))
504            .unwrap_or(&DEFAULT_CONFIG)
505    }
506}
507
508const DEFAULT_CONFIG: MultipartFormConfig = MultipartFormConfig {
509    total_limit: 52_428_800, // 50 MiB
510    memory_limit: 2_097_152, // 2 MiB
511    err_handler: None,
512};
513
514impl Default for MultipartFormConfig {
515    fn default() -> Self {
516        DEFAULT_CONFIG.clone()
517    }
518}
519
520// Work around until https://github.com/actix/actix-web/pull/2885 becomes available
521fn field_mime(field: &Field) -> Option<mime::Mime> {
522    if field.headers().get(&header::CONTENT_TYPE).is_none() {
523        None
524    } else {
525        Some(field.content_type().clone())
526    }
527}
528
529#[cfg(test)]
530mod tests {
531    use super::MultipartForm;
532    use crate::bytes::Bytes;
533    use crate::text::Text;
534    use crate::MultipartFormConfig;
535    use actix_http::encoding::Decoder;
536    use actix_http::Payload;
537    use actix_multipart_rfc7578::client::multipart;
538    use actix_test::TestServer;
539    use actix_web::http::StatusCode;
540    use actix_web::{web, App, HttpResponse, Responder};
541    use awc::{Client, ClientResponse};
542
543    pub async fn send_form(
544        srv: &TestServer,
545        form: multipart::Form<'static>,
546        uri: &'static str,
547    ) -> ClientResponse<Decoder<Payload>> {
548        Client::default()
549            .post(srv.url(uri))
550            .content_type(form.content_type())
551            .send_body(multipart::Body::from(form))
552            .await
553            .unwrap()
554    }
555
556    /// Test `Option` fields
557
558    #[derive(MultipartForm)]
559    struct TestOptions {
560        field1: Option<Text<String>>,
561        field2: Option<Text<String>>,
562    }
563
564    async fn test_options_route(form: MultipartForm<TestOptions>) -> impl Responder {
565        assert!(form.field1.is_some());
566        assert!(form.field2.is_none());
567        HttpResponse::Ok().finish()
568    }
569
570    #[actix_rt::test]
571    async fn test_options() {
572        let srv = actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
573
574        let mut form = multipart::Form::default();
575        form.add_text("field1", "value");
576
577        let response = send_form(&srv, form, "/").await;
578        assert_eq!(response.status(), StatusCode::OK);
579    }
580
581    /// Test `Vec` fields
582
583    #[derive(MultipartForm)]
584    struct TestVec {
585        list1: Vec<Text<String>>,
586        list2: Vec<Text<String>>,
587    }
588
589    async fn test_vec_route(form: MultipartForm<TestVec>) -> impl Responder {
590        let form = form.into_inner();
591        let strings = form
592            .list1
593            .into_iter()
594            .map(|s| s.into_inner())
595            .collect::<Vec<_>>();
596        assert_eq!(strings, vec!["value1", "value2", "value3"]);
597        assert_eq!(form.list2.len(), 0);
598        HttpResponse::Ok().finish()
599    }
600
601    #[actix_rt::test]
602    async fn test_vec() {
603        let srv = actix_test::start(|| App::new().route("/", web::post().to(test_vec_route)));
604
605        let mut form = multipart::Form::default();
606        form.add_text("list1", "value1");
607        form.add_text("list1", "value2");
608        form.add_text("list1", "value3");
609
610        let response = send_form(&srv, form, "/").await;
611        assert_eq!(response.status(), StatusCode::OK);
612    }
613
614    /// Test the `rename` field attribute
615
616    #[derive(MultipartForm)]
617    struct TestFieldRenaming {
618        #[multipart(rename = "renamed")]
619        field1: Text<String>,
620        #[multipart(rename = "field1")]
621        field2: Text<String>,
622        field3: Text<String>,
623    }
624
625    async fn test_field_renaming_route(form: MultipartForm<TestFieldRenaming>) -> impl Responder {
626        assert_eq!(&*form.field1, "renamed");
627        assert_eq!(&*form.field2, "field1");
628        assert_eq!(&*form.field3, "field3");
629        HttpResponse::Ok().finish()
630    }
631
632    #[actix_rt::test]
633    async fn test_field_renaming() {
634        let srv =
635            actix_test::start(|| App::new().route("/", web::post().to(test_field_renaming_route)));
636
637        let mut form = multipart::Form::default();
638        form.add_text("renamed", "renamed");
639        form.add_text("field1", "field1");
640        form.add_text("field3", "field3");
641
642        let response = send_form(&srv, form, "/").await;
643        assert_eq!(response.status(), StatusCode::OK);
644    }
645
646    /// Test the `deny_unknown_fields` struct attribute
647
648    #[derive(MultipartForm)]
649    #[multipart(deny_unknown_fields)]
650    struct TestDenyUnknown {}
651
652    #[derive(MultipartForm)]
653    struct TestAllowUnknown {}
654
655    async fn test_deny_unknown_route(_: MultipartForm<TestDenyUnknown>) -> impl Responder {
656        HttpResponse::Ok().finish()
657    }
658
659    async fn test_allow_unknown_route(_: MultipartForm<TestAllowUnknown>) -> impl Responder {
660        HttpResponse::Ok().finish()
661    }
662
663    #[actix_rt::test]
664    async fn test_deny_unknown() {
665        let srv = actix_test::start(|| {
666            App::new()
667                .route("/deny", web::post().to(test_deny_unknown_route))
668                .route("/allow", web::post().to(test_allow_unknown_route))
669        });
670
671        let mut form = multipart::Form::default();
672        form.add_text("unknown", "value");
673        let response = send_form(&srv, form, "/deny").await;
674        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
675
676        let mut form = multipart::Form::default();
677        form.add_text("unknown", "value");
678        let response = send_form(&srv, form, "/allow").await;
679        assert_eq!(response.status(), StatusCode::OK);
680    }
681
682    /// Test the `duplicate_action` struct attribute
683
684    #[derive(MultipartForm)]
685    #[multipart(duplicate_action = "deny")]
686    struct TestDuplicateDeny {
687        _field: Text<String>,
688    }
689
690    #[derive(MultipartForm)]
691    #[multipart(duplicate_action = "replace")]
692    struct TestDuplicateReplace {
693        field: Text<String>,
694    }
695
696    #[derive(MultipartForm)]
697    #[multipart(duplicate_action = "ignore")]
698    struct TestDuplicateIgnore {
699        field: Text<String>,
700    }
701
702    async fn test_duplicate_deny_route(_: MultipartForm<TestDuplicateDeny>) -> impl Responder {
703        HttpResponse::Ok().finish()
704    }
705
706    async fn test_duplicate_replace_route(
707        form: MultipartForm<TestDuplicateReplace>,
708    ) -> impl Responder {
709        assert_eq!(&*form.field, "second_value");
710        HttpResponse::Ok().finish()
711    }
712
713    async fn test_duplicate_ignore_route(
714        form: MultipartForm<TestDuplicateIgnore>,
715    ) -> impl Responder {
716        assert_eq!(&*form.field, "first_value");
717        HttpResponse::Ok().finish()
718    }
719
720    #[actix_rt::test]
721    async fn test_duplicate_action() {
722        let srv = actix_test::start(|| {
723            App::new()
724                .route("/deny", web::post().to(test_duplicate_deny_route))
725                .route("/replace", web::post().to(test_duplicate_replace_route))
726                .route("/ignore", web::post().to(test_duplicate_ignore_route))
727        });
728
729        let mut form = multipart::Form::default();
730        form.add_text("_field", "first_value");
731        form.add_text("_field", "second_value");
732        let response = send_form(&srv, form, "/deny").await;
733        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
734
735        let mut form = multipart::Form::default();
736        form.add_text("field", "first_value");
737        form.add_text("field", "second_value");
738        let response = send_form(&srv, form, "/replace").await;
739        assert_eq!(response.status(), StatusCode::OK);
740
741        let mut form = multipart::Form::default();
742        form.add_text("field", "first_value");
743        form.add_text("field", "second_value");
744        let response = send_form(&srv, form, "/ignore").await;
745        assert_eq!(response.status(), StatusCode::OK);
746    }
747
748    /// Test the Limits
749
750    #[derive(MultipartForm)]
751    struct TestMemoryUploadLimits {
752        field: Bytes,
753    }
754
755    #[derive(MultipartForm)]
756    struct TestFileUploadLimits {
757        #[cfg(feature = "tempfile")]
758        field: crate::tempfile::Tempfile,
759    }
760
761    async fn test_upload_limits_memory(
762        form: MultipartForm<TestMemoryUploadLimits>,
763    ) -> impl Responder {
764        assert!(form.field.data.len() > 0);
765        HttpResponse::Ok().finish()
766    }
767
768    #[allow(unused_variables)]
769    async fn test_upload_limits_file(form: MultipartForm<TestFileUploadLimits>) -> impl Responder {
770        #[cfg(feature = "tempfile")]
771        assert!(form.field.size > 0);
772        HttpResponse::Ok().finish()
773    }
774
775    #[actix_rt::test]
776    async fn test_memory_limits() {
777        let srv = actix_test::start(|| {
778            App::new()
779                .route("/text", web::post().to(test_upload_limits_memory))
780                .route("/file", web::post().to(test_upload_limits_file))
781                .app_data(
782                    MultipartFormConfig::default()
783                        .memory_limit(20)
784                        .total_limit(usize::MAX),
785                )
786        });
787
788        // Exceeds the 20 byte memory limit
789        let mut form = multipart::Form::default();
790        form.add_text("field", "this string is 28 bytes long");
791        let response = send_form(&srv, form, "/text").await;
792        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
793
794        // Memory limit should not apply when the data is being streamed to disk
795        let mut form = multipart::Form::default();
796        form.add_text("field", "this string is 28 bytes long");
797        let response = send_form(&srv, form, "/file").await;
798        assert_eq!(response.status(), StatusCode::OK);
799    }
800
801    #[actix_rt::test]
802    #[cfg(feature = "tempfile")]
803    async fn test_total_limit() {
804        let srv = actix_test::start(|| {
805            App::new()
806                .route("/text", web::post().to(test_upload_limits_memory))
807                .route("/file", web::post().to(test_upload_limits_file))
808                .app_data(
809                    MultipartFormConfig::default()
810                        .memory_limit(usize::MAX)
811                        .total_limit(20),
812                )
813        });
814
815        // Within the 20 byte limit
816        let mut form = multipart::Form::default();
817        form.add_text("field", "7 bytes");
818        let response = send_form(&srv, form, "/text").await;
819        assert_eq!(response.status(), StatusCode::OK);
820
821        // Exceeds the 20 byte overall limit
822        let mut form = multipart::Form::default();
823        form.add_text("field", "this string is 28 bytes long");
824        let response = send_form(&srv, form, "/text").await;
825        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
826
827        // Exceeds the 20 byte overall limit
828        let mut form = multipart::Form::default();
829        form.add_text("field", "this string is 28 bytes long");
830        let response = send_form(&srv, form, "/file").await;
831        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
832    }
833
834    #[derive(MultipartForm)]
835    struct TestFieldLevelLimits {
836        #[multipart(limit = "30B")]
837        field: Vec<Bytes>,
838    }
839
840    async fn test_field_level_limits_route(
841        form: MultipartForm<TestFieldLevelLimits>,
842    ) -> impl Responder {
843        assert!(form.field.len() > 0);
844        HttpResponse::Ok().finish()
845    }
846
847    #[actix_rt::test]
848    async fn test_field_level_limits() {
849        let srv = actix_test::start(|| {
850            App::new()
851                .route("/", web::post().to(test_field_level_limits_route))
852                .app_data(
853                    MultipartFormConfig::default()
854                        .memory_limit(usize::MAX)
855                        .total_limit(usize::MAX),
856                )
857        });
858
859        // Within the 30 byte limit
860        let mut form = multipart::Form::default();
861        form.add_text("field", "this string is 28 bytes long");
862        let response = send_form(&srv, form, "/").await;
863        assert_eq!(response.status(), StatusCode::OK);
864
865        // Exceeds the the 30 byte limit
866        let mut form = multipart::Form::default();
867        form.add_text("field", "this string is more than 30 bytes long");
868        let response = send_form(&srv, form, "/").await;
869        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
870
871        // Total of values (14 bytes) is within 30 byte limit for "field"
872        let mut form = multipart::Form::default();
873        form.add_text("field", "7 bytes");
874        form.add_text("field", "7 bytes");
875        let response = send_form(&srv, form, "/").await;
876        assert_eq!(response.status(), StatusCode::OK);
877
878        // Total of values exceeds 30 byte limit for "field"
879        let mut form = multipart::Form::default();
880        form.add_text("field", "this string is 28 bytes long");
881        form.add_text("field", "this string is 28 bytes long");
882        let response = send_form(&srv, form, "/").await;
883        assert_eq!(response.status(), StatusCode::BAD_REQUEST);
884    }
885}