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