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}