Skip to main content

form_net/
limits.rs

1//! Budgets ([`Limits`]) and behaviours ([`Policy`]).
2//!
3//! Both are small `Copy` values with `const` setters, so a route's
4//! configuration is a `const` checked once at startup.
5
6use crate::error::{FormError, Limit};
7
8/// The bytes the multipart parser keeps for the `_charset_` field's value.
9pub(crate) const CHARSET_CAPTURE: usize = 32;
10
11/// Every budget form-net enforces.
12///
13/// Each budget is checked before the bytes it governs are buffered, stored,
14/// or passed on. A limit of 0 means none allowed. A limit larger than the one
15/// containing it is capped by it (the effective value is the smaller), so a
16/// route sets only the limits it cares about.
17///
18/// | Field | Default |
19/// |---|---|
20/// | `max_body_bytes` | 10 MiB |
21/// | `max_fields` | 1,000 |
22/// | `max_name_bytes` | 256 |
23/// | `max_value_bytes` | 1 MiB |
24/// | `max_text_bytes` | 2 MiB |
25/// | `max_parts` | 100 |
26/// | `max_files` | 20 |
27/// | `max_file_bytes` | 10 MiB |
28/// | `max_part_header_bytes` | 8 KiB |
29/// | `max_part_headers` | 8 |
30/// | `max_filename_bytes` | 1,024 |
31/// | `max_preamble_bytes` | 1 KiB |
32/// | `max_epilogue_bytes` | 1 KiB |
33/// | `max_padding_bytes` | 32 |
34/// | `sniff_bytes` | 0 |
35/// | `max_token_bytes` | 128 |
36///
37/// # Examples
38///
39/// ```
40/// use form_net::Limits;
41///
42/// // A login route: a small body, a few fields, no files.
43/// const LOGIN: Limits = Limits::DEFAULT
44///     .with_max_body_bytes(4 * 1024)
45///     .with_max_fields(8)
46///     .with_max_files(0);
47/// assert!(LOGIN.validate().is_ok());
48/// ```
49#[non_exhaustive]
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct Limits {
52    /// Every byte fed, both formats, preamble and epilogue included.
53    pub max_body_bytes: u64,
54    /// urlencoded pairs; multipart text fields.
55    pub max_fields: u32,
56    /// Raw (encoded) name length. Capped by `max_body_bytes`.
57    pub max_name_bytes: u32,
58    /// One urlencoded value or one multipart text field. Capped by
59    /// `max_text_bytes`.
60    pub max_value_bytes: u32,
61    /// All text values of one body together. Capped by `max_body_bytes`.
62    pub max_text_bytes: u64,
63    /// Multipart parts of either kind.
64    pub max_parts: u32,
65    /// Multipart file parts.
66    pub max_files: u32,
67    /// One file part. Capped by `max_body_bytes`.
68    pub max_file_bytes: u64,
69    /// One part's header block, CRLF CRLF included (at least 64).
70    pub max_part_header_bytes: u32,
71    /// Header lines per part.
72    pub max_part_headers: u16,
73    /// A raw `filename`, or a decoded `filename*`. Capped by
74    /// `max_part_header_bytes`.
75    pub max_filename_bytes: u32,
76    /// Bytes before the first delimiter. Capped by `max_body_bytes`.
77    pub max_preamble_bytes: u32,
78    /// Bytes after the close delimiter. Capped by `max_body_bytes`.
79    pub max_epilogue_bytes: u32,
80    /// Transport padding after a delimiter.
81    pub max_padding_bytes: u8,
82    /// The sniff window per file part (0: off). Capped by `max_file_bytes`.
83    pub sniff_bytes: u32,
84    /// The captured anti-forgery token. Capped by `max_value_bytes`.
85    pub max_token_bytes: u16,
86}
87
88macro_rules! setter {
89    ($(#[$doc:meta])* $name:ident, $field:ident, $ty:ty) => {
90        $(#[$doc])*
91        #[must_use]
92        pub const fn $name(mut self, value: $ty) -> Self {
93            self.$field = value;
94            self
95        }
96    };
97}
98
99const fn min_u64(a: u64, b: u64) -> u64 {
100    if a < b { a } else { b }
101}
102
103/// `value` as `usize`, or `usize::MAX` when it does not fit.
104const fn to_usize(value: u64) -> usize {
105    if value > usize::MAX as u64 {
106        usize::MAX
107    } else {
108        value as usize
109    }
110}
111
112const fn add_usize(a: usize, b: usize) -> usize {
113    match a.checked_add(b) {
114        Some(v) => v,
115        None => usize::MAX,
116    }
117}
118
119impl Limits {
120    /// The defaults in the table above.
121    pub const DEFAULT: Limits = Limits {
122        max_body_bytes: 10 * 1024 * 1024,
123        max_fields: 1_000,
124        max_name_bytes: 256,
125        max_value_bytes: 1024 * 1024,
126        max_text_bytes: 2 * 1024 * 1024,
127        max_parts: 100,
128        max_files: 20,
129        max_file_bytes: 10 * 1024 * 1024,
130        max_part_header_bytes: 8 * 1024,
131        max_part_headers: 8,
132        max_filename_bytes: 1_024,
133        max_preamble_bytes: 1_024,
134        max_epilogue_bytes: 1_024,
135        max_padding_bytes: 32,
136        sniff_bytes: 0,
137        max_token_bytes: 128,
138    };
139
140    setter!(
141        /// Sets `max_body_bytes`.
142        with_max_body_bytes, max_body_bytes, u64);
143    setter!(
144        /// Sets `max_fields`.
145        with_max_fields, max_fields, u32);
146    setter!(
147        /// Sets `max_name_bytes`.
148        with_max_name_bytes, max_name_bytes, u32);
149    setter!(
150        /// Sets `max_value_bytes`.
151        with_max_value_bytes, max_value_bytes, u32);
152    setter!(
153        /// Sets `max_text_bytes`.
154        with_max_text_bytes, max_text_bytes, u64);
155    setter!(
156        /// Sets `max_parts`.
157        with_max_parts, max_parts, u32);
158    setter!(
159        /// Sets `max_files`.
160        with_max_files, max_files, u32);
161    setter!(
162        /// Sets `max_file_bytes`.
163        with_max_file_bytes, max_file_bytes, u64);
164    setter!(
165        /// Sets `max_part_header_bytes`.
166        with_max_part_header_bytes, max_part_header_bytes, u32);
167    setter!(
168        /// Sets `max_part_headers`.
169        with_max_part_headers, max_part_headers, u16);
170    setter!(
171        /// Sets `max_filename_bytes`.
172        with_max_filename_bytes, max_filename_bytes, u32);
173    setter!(
174        /// Sets `max_preamble_bytes`.
175        with_max_preamble_bytes, max_preamble_bytes, u32);
176    setter!(
177        /// Sets `max_epilogue_bytes`.
178        with_max_epilogue_bytes, max_epilogue_bytes, u32);
179    setter!(
180        /// Sets `max_padding_bytes`.
181        with_max_padding_bytes, max_padding_bytes, u8);
182    setter!(
183        /// Sets `sniff_bytes`.
184        with_sniff_bytes, sniff_bytes, u32);
185    setter!(
186        /// Sets `max_token_bytes`.
187        with_max_token_bytes, max_token_bytes, u16);
188
189    /// Refuses the values no body could satisfy, so a misconfiguration is
190    /// found at startup: a `max_part_header_bytes` below 64 (the shortest
191    /// useful `Content-Disposition` line must fit), or buffer sizes that do
192    /// not fit in `usize`. Every parser constructor and every `*_with`
193    /// function calls it.
194    ///
195    /// # Errors
196    ///
197    /// [`FormError::InvalidLimits`] naming the refused field.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use form_net::Limits;
203    ///
204    /// assert!(Limits::DEFAULT.validate().is_ok());
205    /// let broken = Limits::DEFAULT.with_max_part_header_bytes(16);
206    /// assert_eq!(broken.validate().unwrap_err().code(), "invalid_limits");
207    /// ```
208    pub fn validate(&self) -> Result<(), FormError> {
209        if self.max_part_header_bytes < 64 {
210            return Err(FormError::InvalidLimits {
211                limit: Limit::PartHeaderBytes,
212            });
213        }
214        let parts = [
215            u64::from(self.max_part_header_bytes),
216            u64::from(self.name()),
217            u64::from(self.filename()),
218            u64::from(self.token()),
219            CHARSET_CAPTURE as u64,
220        ];
221        let total = parts.iter().fold(0u64, |a, &b| a.saturating_add(b));
222        if total > usize::MAX as u64 {
223            return Err(FormError::InvalidLimits {
224                limit: Limit::PartHeaderBytes,
225            });
226        }
227        let url = u64::from(self.name())
228            .saturating_add(u64::from(self.value()))
229            .saturating_add(1);
230        if url > usize::MAX as u64 {
231            return Err(FormError::InvalidLimits {
232                limit: Limit::ValueLength,
233            });
234        }
235        Ok(())
236    }
237
238    /// Validates these limits together with a policy: a token field with an
239    /// effective `max_token_bytes` of 0 can never be captured.
240    pub(crate) fn validate_with(&self, policy: &crate::Policy) -> Result<(), FormError> {
241        self.validate()?;
242        if policy.token_field.is_some() && self.token() == 0 {
243            return Err(FormError::InvalidLimits {
244                limit: Limit::TokenLength,
245            });
246        }
247        Ok(())
248    }
249
250    /// The buffer length the multipart parser needs with these limits and
251    /// this policy: the header block, the decoded field name, the decoded
252    /// `filename*` (only under `ExtendedFilenamePolicy::AcceptAlone`), the
253    /// token (only with a token field), and 32 bytes for `_charset_`.
254    ///
255    /// A `const fn`, so a stack buffer can be sized at compile time.
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use form_net::{Limits, Policy};
261    ///
262    /// const LIMITS: Limits = Limits::DEFAULT.with_max_part_header_bytes(1024);
263    /// const LEN: usize = LIMITS.multipart_buffer_len(&Policy::DEFAULT);
264    /// assert_eq!(LEN, 1024 + 256 + 32);
265    /// let buffer = [0u8; LEN];
266    /// ```
267    #[must_use]
268    pub const fn multipart_buffer_len(&self, policy: &crate::Policy) -> usize {
269        let mut len = add_usize(self.max_part_header_bytes as usize, self.name() as usize);
270        if matches!(
271            policy.extended_filename,
272            crate::ExtendedFilenamePolicy::AcceptAlone
273        ) {
274            len = add_usize(len, self.filename() as usize);
275        }
276        if policy.token_field.is_some() {
277            len = add_usize(len, self.token() as usize);
278        }
279        add_usize(len, CHARSET_CAPTURE)
280    }
281
282    /// The scratch length the streaming urlencoded parser needs:
283    /// `max_name_bytes + max_value_bytes + 1` (effective values).
284    ///
285    /// # Examples
286    ///
287    /// ```
288    /// use form_net::Limits;
289    ///
290    /// const LIMITS: Limits = Limits::DEFAULT.with_max_text_bytes(1000);
291    /// assert_eq!(LIMITS.urlencoded_buffer_len(), 256 + 1000 + 1);
292    /// ```
293    #[must_use]
294    pub const fn urlencoded_buffer_len(&self) -> usize {
295        add_usize(add_usize(self.name() as usize, self.value() as usize), 1)
296    }
297
298    /// The worst-case bytes one parse with these limits can hold in memory,
299    /// for admission control before a body is read (FR-40, D-093).
300    ///
301    /// The sum of: parser state (256); the parser buffer and the driver's
302    /// copy of the current part head (each `multipart_buffer_len` under the
303    /// most demanding policy); the sniff window; the field being received
304    /// (`max_value_bytes`); the text kept (`max_text_bytes`) and its names
305    /// (`max_fields × max_name_bytes`, capped by `max_body_bytes`); 96 bytes
306    /// of index per field and 320 per part; owned copies of part names, file
307    /// names, and content types (`max_parts × max_part_header_bytes`, capped
308    /// by `max_body_bytes`); the files kept in memory,
309    /// `max_files × min(file_in_memory_bytes, max_file_bytes)`, capped by
310    /// twice `max_body_bytes` (a buffer that grew by doubling can hold twice
311    /// its bytes); and half the largest growing buffer (a buffer that
312    /// doubles holds its old half while it moves). `file_in_memory_bytes`
313    /// is the most bytes of one file a store keeps in memory
314    /// (`Store::memory_bound` passes it). The caller's own read buffer is
315    /// not included. All values are effective values; the arithmetic
316    /// saturates. `tests/memory_bound.rs` measures it against hostile bodies.
317    ///
318    /// # Examples
319    ///
320    /// ```
321    /// use form_net::Limits;
322    ///
323    /// const LOGIN: Limits = Limits::DEFAULT
324    ///     .with_max_body_bytes(4 * 1024)
325    ///     .with_max_fields(8)
326    ///     .with_max_parts(8)
327    ///     .with_max_files(0)
328    ///     .with_max_part_header_bytes(1024);
329    /// assert!(LOGIN.memory_bound(64 * 1024) < 32 * 1024);
330    /// // The defaults with files over 64 KiB spilled: about 6.5 MiB.
331    /// assert!(Limits::DEFAULT.memory_bound(64 * 1024) < 7 << 20);
332    /// ```
333    #[must_use]
334    pub const fn memory_bound(&self, file_in_memory_bytes: u64) -> u64 {
335        const PARSER_STATE: u64 = 256;
336        const FIELD_INDEX: u64 = 96;
337        const PART_INDEX: u64 = 320;
338        let head = (self.max_part_header_bytes as u64)
339            .saturating_add(self.name() as u64)
340            .saturating_add(self.filename() as u64)
341            .saturating_add(self.token() as u64)
342            .saturating_add(CHARSET_CAPTURE as u64);
343        let parts = self.max_parts as u64;
344        let copies = min_u64(
345            parts.saturating_mul(self.max_part_header_bytes as u64),
346            self.body(),
347        );
348        let sniff = min_u64(self.sniff_bytes as u64, self.file());
349        let file = min_u64(file_in_memory_bytes, self.file());
350        let files = min_u64(
351            (self.max_files as u64).saturating_mul(file),
352            self.body().saturating_mul(2),
353        );
354        let text = self.text_and_names();
355        let growth = if file > text { file } else { text } / 2;
356        PARSER_STATE
357            .saturating_add(head.saturating_mul(2))
358            .saturating_add(sniff)
359            .saturating_add(self.value() as u64)
360            .saturating_add(text)
361            .saturating_add((self.max_fields as u64).saturating_mul(FIELD_INDEX))
362            .saturating_add(parts.saturating_mul(PART_INDEX))
363            .saturating_add(copies)
364            .saturating_add(files)
365            .saturating_add(growth)
366    }
367
368    /// The text values and field names one body can hold: `max_text_bytes`
369    /// plus `max_fields × max_name_bytes`, the latter capped by
370    /// `max_body_bytes` (effective values).
371    pub(crate) const fn text_and_names(&self) -> u64 {
372        let names = min_u64(
373            (self.max_fields as u64).saturating_mul(self.name() as u64),
374            self.body(),
375        );
376        self.text().saturating_add(names)
377    }
378
379    // Effective values: each capped by the limit that contains it.
380
381    pub(crate) const fn body(&self) -> u64 {
382        self.max_body_bytes
383    }
384
385    pub(crate) const fn text(&self) -> u64 {
386        min_u64(self.max_text_bytes, self.body())
387    }
388
389    pub(crate) const fn value(&self) -> u32 {
390        min_u64(self.max_value_bytes as u64, self.text()) as u32
391    }
392
393    pub(crate) const fn name(&self) -> u32 {
394        min_u64(self.max_name_bytes as u64, self.body()) as u32
395    }
396
397    pub(crate) const fn file(&self) -> u64 {
398        min_u64(self.max_file_bytes, self.body())
399    }
400
401    #[cfg(feature = "alloc")]
402    pub(crate) const fn sniff(&self) -> u32 {
403        min_u64(self.sniff_bytes as u64, self.file()) as u32
404    }
405
406    pub(crate) const fn filename(&self) -> u32 {
407        min_u64(
408            self.max_filename_bytes as u64,
409            self.max_part_header_bytes as u64,
410        ) as u32
411    }
412
413    pub(crate) const fn token(&self) -> u16 {
414        min_u64(self.max_token_bytes as u64, self.value() as u64) as u16
415    }
416
417    pub(crate) const fn preamble(&self) -> u64 {
418        min_u64(self.max_preamble_bytes as u64, self.body())
419    }
420
421    pub(crate) const fn epilogue(&self) -> u64 {
422        min_u64(self.max_epilogue_bytes as u64, self.body())
423    }
424
425    pub(crate) const fn body_usize(&self) -> usize {
426        to_usize(self.body())
427    }
428}
429
430impl Default for Limits {
431    fn default() -> Self {
432        Self::DEFAULT
433    }
434}
435
436/// The behaviours a route may choose. The defaults are the strict ones;
437/// every alternative is named after what it allows.
438///
439/// # Examples
440///
441/// ```
442/// use form_net::{EmptyFilePolicy, HeaderPolicy, Policy, TokenField};
443///
444/// const UPLOADS: Policy = Policy::DEFAULT
445///     .with_token_field(TokenField::new("csrf_token"))
446///     .with_empty_files(EmptyFilePolicy::Skip)
447///     .with_unknown_headers(HeaderPolicy::Refuse);
448/// assert_eq!(UPLOADS.token_field.map(|t| t.name()), Some("csrf_token"));
449/// ```
450#[non_exhaustive]
451#[derive(Clone, Copy, Debug, PartialEq, Eq)]
452pub struct Policy {
453    /// A malformed `%` in urlencoded input. Default `Refuse`.
454    pub percent: PercentPolicy,
455    /// `filename*` in a part. Default `Refuse`.
456    pub extended_filename: ExtendedFilenamePolicy,
457    /// Part headers other than the three known ones. Default `Ignore`.
458    pub unknown_headers: HeaderPolicy,
459    /// A file input left empty. Default `Report`.
460    pub empty_files: EmptyFilePolicy,
461    /// The anti-forgery token field to capture. Default none.
462    pub token_field: Option<TokenField>,
463}
464
465impl Policy {
466    /// The strict defaults.
467    pub const DEFAULT: Policy = Policy {
468        percent: PercentPolicy::Refuse,
469        extended_filename: ExtendedFilenamePolicy::Refuse,
470        unknown_headers: HeaderPolicy::Ignore,
471        empty_files: EmptyFilePolicy::Report,
472        token_field: None,
473    };
474
475    /// Sets `percent`.
476    #[must_use]
477    pub const fn with_percent(mut self, policy: PercentPolicy) -> Self {
478        self.percent = policy;
479        self
480    }
481
482    /// Sets `extended_filename`.
483    #[must_use]
484    pub const fn with_extended_filename(mut self, policy: ExtendedFilenamePolicy) -> Self {
485        self.extended_filename = policy;
486        self
487    }
488
489    /// Sets `unknown_headers`.
490    #[must_use]
491    pub const fn with_unknown_headers(mut self, policy: HeaderPolicy) -> Self {
492        self.unknown_headers = policy;
493        self
494    }
495
496    /// Sets `empty_files`.
497    #[must_use]
498    pub const fn with_empty_files(mut self, policy: EmptyFilePolicy) -> Self {
499        self.empty_files = policy;
500        self
501    }
502
503    /// Sets the token field to capture.
504    #[must_use]
505    pub const fn with_token_field(mut self, field: TokenField) -> Self {
506        self.token_field = Some(field);
507        self
508    }
509}
510
511impl Default for Policy {
512    fn default() -> Self {
513        Self::DEFAULT
514    }
515}
516
517/// What a `%` not followed by two hex digits means in urlencoded input.
518///
519/// # Examples
520///
521/// ```
522/// use form_net::{decode_into, PercentPolicy};
523///
524/// let mut out = [0u8; 8];
525/// assert!(decode_into(b"100%", &mut out, PercentPolicy::Refuse).is_err());
526/// assert_eq!(decode_into(b"100%", &mut out, PercentPolicy::Literal)?, "100%");
527/// # Ok::<(), form_net::FormError>(())
528/// ```
529#[non_exhaustive]
530#[derive(Clone, Copy, Debug, PartialEq, Eq)]
531pub enum PercentPolicy {
532    /// Refuse it (default).
533    Refuse,
534    /// Keep it as a literal byte, as WHATWG percent-decode does.
535    Literal,
536}
537
538/// What a `filename*` parameter (RFC 8187) in a part means. Browsers never
539/// send it; RFC 7578 forbids senders from using it.
540///
541/// Both `filename` and `filename*` in one part are refused under every
542/// policy.
543///
544/// # Examples
545///
546/// ```
547/// use form_net::{ExtendedFilenamePolicy, Policy};
548///
549/// let accept = Policy::DEFAULT.with_extended_filename(ExtendedFilenamePolicy::AcceptAlone);
550/// assert_eq!(Policy::DEFAULT.extended_filename, ExtendedFilenamePolicy::Refuse);
551/// assert_ne!(accept, Policy::DEFAULT);
552/// ```
553#[non_exhaustive]
554#[derive(Clone, Copy, Debug, PartialEq, Eq)]
555pub enum ExtendedFilenamePolicy {
556    /// Refuse any `filename*` (default).
557    Refuse,
558    /// Accept and decode it (UTF-8 only) when it is the only file name.
559    AcceptAlone,
560    /// Drop it; a part with only `filename*` is then a text field.
561    Ignore,
562}
563
564/// Part headers other than `Content-Disposition`, `Content-Type`, and
565/// `Content-Transfer-Encoding`.
566///
567/// # Examples
568///
569/// ```
570/// use form_net::{HeaderPolicy, Policy};
571///
572/// assert_eq!(Policy::DEFAULT.unknown_headers, HeaderPolicy::Ignore);
573/// ```
574#[non_exhaustive]
575#[derive(Clone, Copy, Debug, PartialEq, Eq)]
576pub enum HeaderPolicy {
577    /// Skip them, as RFC 7578 section 4.8 requires (default). They still
578    /// count toward the header budgets.
579    Ignore,
580    /// Refuse them.
581    Refuse,
582}
583
584/// A file input left empty (`filename=""` and no data).
585///
586/// # Examples
587///
588/// ```
589/// use form_net::{EmptyFilePolicy, Policy};
590///
591/// assert_eq!(Policy::DEFAULT.empty_files, EmptyFilePolicy::Report);
592/// ```
593#[non_exhaustive]
594#[derive(Clone, Copy, Debug, PartialEq, Eq)]
595pub enum EmptyFilePolicy {
596    /// Report it as a file part with an empty file name (default).
597    Report,
598    /// Produce nothing for it; it does not count against `max_files`.
599    Skip,
600}
601
602/// The anti-forgery token field to capture from a multipart body.
603///
604/// # Examples
605///
606/// ```
607/// use form_net::{TokenField, TokenPlacement};
608///
609/// let strict = TokenField::new("csrf_token");
610/// assert_eq!(strict.placement(), TokenPlacement::BeforeFirstFile);
611/// assert_eq!(strict.anywhere().placement(), TokenPlacement::Anywhere);
612/// ```
613#[derive(Clone, Copy, Debug, PartialEq, Eq)]
614pub struct TokenField {
615    name: &'static str,
616    placement: TokenPlacement,
617}
618
619impl TokenField {
620    /// A token field named `name` that must come before the first file.
621    #[must_use]
622    pub const fn new(name: &'static str) -> TokenField {
623        TokenField {
624            name,
625            placement: TokenPlacement::BeforeFirstFile,
626        }
627    }
628
629    /// The same field, allowed anywhere in the body.
630    #[must_use]
631    pub const fn anywhere(self) -> TokenField {
632        TokenField {
633            name: self.name,
634            placement: TokenPlacement::Anywhere,
635        }
636    }
637
638    /// The field name, matched byte for byte.
639    #[must_use]
640    pub const fn name(&self) -> &'static str {
641        self.name
642    }
643
644    /// Where the field must be.
645    #[must_use]
646    pub const fn placement(&self) -> TokenPlacement {
647        self.placement
648    }
649}
650
651/// Where the token field must be.
652#[non_exhaustive]
653#[derive(Clone, Copy, Debug, PartialEq, Eq)]
654pub enum TokenPlacement {
655    /// Before the first file part (default of [`TokenField::new`]).
656    BeforeFirstFile,
657    /// Anywhere.
658    Anywhere,
659}
660
661#[cfg(test)]
662mod tests {
663    use super::*;
664
665    #[test]
666    fn test_limits_default_validates() {
667        assert!(Limits::DEFAULT.validate().is_ok());
668        assert_eq!(Limits::default(), Limits::DEFAULT);
669    }
670
671    #[test]
672    fn test_limits_header_budget_below_64_refused() {
673        for bytes in [0, 1, 63] {
674            let l = Limits::DEFAULT.with_max_part_header_bytes(bytes);
675            assert_eq!(
676                l.validate(),
677                Err(FormError::InvalidLimits {
678                    limit: Limit::PartHeaderBytes
679                })
680            );
681        }
682        assert!(
683            Limits::DEFAULT
684                .with_max_part_header_bytes(64)
685                .validate()
686                .is_ok()
687        );
688    }
689
690    #[test]
691    fn test_limits_containing_limit_caps_contained() {
692        let l = Limits::DEFAULT.with_max_body_bytes(4096);
693        assert_eq!(l.text(), 4096);
694        assert_eq!(l.value(), 4096);
695        assert_eq!(l.file(), 4096);
696        assert_eq!(l.name(), 256);
697        assert_eq!(l.token(), 128);
698        let l = l.with_max_value_bytes(10);
699        assert_eq!((l.value(), l.token()), (10, 10));
700        let l = Limits::DEFAULT.with_max_part_header_bytes(100);
701        assert_eq!(l.filename(), 100);
702        let l = Limits::DEFAULT.with_sniff_bytes(64).with_max_file_bytes(16);
703        assert!(l.validate().is_ok());
704    }
705
706    #[test]
707    fn test_limits_zero_token_with_token_field_refused() {
708        let policy = Policy::DEFAULT.with_token_field(TokenField::new("t"));
709        let l = Limits::DEFAULT.with_max_token_bytes(0);
710        assert_eq!(
711            l.validate_with(&policy),
712            Err(FormError::InvalidLimits {
713                limit: Limit::TokenLength
714            })
715        );
716        assert!(l.validate_with(&Policy::DEFAULT).is_ok());
717    }
718
719    #[test]
720    fn test_buffer_lengths_follow_policy() {
721        let l = Limits::DEFAULT;
722        assert_eq!(l.multipart_buffer_len(&Policy::DEFAULT), 8192 + 256 + 32);
723        let p = Policy::DEFAULT
724            .with_extended_filename(ExtendedFilenamePolicy::AcceptAlone)
725            .with_token_field(TokenField::new("t"));
726        assert_eq!(l.multipart_buffer_len(&p), 8192 + 256 + 1024 + 128 + 32);
727        assert_eq!(l.urlencoded_buffer_len(), 256 + 1024 * 1024 + 1);
728    }
729
730    #[test]
731    fn test_setters_set_one_field_each() {
732        let l = Limits::DEFAULT
733            .with_max_body_bytes(1)
734            .with_max_fields(2)
735            .with_max_name_bytes(3)
736            .with_max_value_bytes(4)
737            .with_max_text_bytes(5)
738            .with_max_parts(6)
739            .with_max_files(7)
740            .with_max_file_bytes(8)
741            .with_max_part_header_bytes(9)
742            .with_max_part_headers(10)
743            .with_max_filename_bytes(11)
744            .with_max_preamble_bytes(12)
745            .with_max_epilogue_bytes(13)
746            .with_max_padding_bytes(14)
747            .with_sniff_bytes(15)
748            .with_max_token_bytes(16);
749        assert_eq!(
750            (
751                l.max_body_bytes,
752                l.max_fields,
753                l.max_name_bytes,
754                l.max_value_bytes
755            ),
756            (1, 2, 3, 4)
757        );
758        assert_eq!(
759            (l.max_text_bytes, l.max_parts, l.max_files, l.max_file_bytes),
760            (5, 6, 7, 8)
761        );
762        assert_eq!(
763            (
764                l.max_part_header_bytes,
765                l.max_part_headers,
766                l.max_filename_bytes,
767                l.max_preamble_bytes
768            ),
769            (9, 10, 11, 12)
770        );
771        assert_eq!(
772            (
773                l.max_epilogue_bytes,
774                l.max_padding_bytes,
775                l.sniff_bytes,
776                l.max_token_bytes
777            ),
778            (13, 14, 15, 16)
779        );
780    }
781
782    #[test]
783    fn test_policy_setters() {
784        let p = Policy::DEFAULT
785            .with_percent(PercentPolicy::Literal)
786            .with_extended_filename(ExtendedFilenamePolicy::Ignore)
787            .with_unknown_headers(HeaderPolicy::Refuse)
788            .with_empty_files(EmptyFilePolicy::Skip)
789            .with_token_field(TokenField::new("t").anywhere());
790        assert_eq!(p.percent, PercentPolicy::Literal);
791        assert_eq!(p.extended_filename, ExtendedFilenamePolicy::Ignore);
792        assert_eq!(p.unknown_headers, HeaderPolicy::Refuse);
793        assert_eq!(p.empty_files, EmptyFilePolicy::Skip);
794        assert_eq!(
795            p.token_field.map(|t| (t.name(), t.placement())),
796            Some(("t", TokenPlacement::Anywhere))
797        );
798        assert_eq!(Policy::default(), Policy::DEFAULT);
799    }
800}