1use crate::error::{FormError, Limit};
7
8pub(crate) const CHARSET_CAPTURE: usize = 32;
10
11#[non_exhaustive]
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct Limits {
52 pub max_body_bytes: u64,
54 pub max_fields: u32,
56 pub max_name_bytes: u32,
58 pub max_value_bytes: u32,
61 pub max_text_bytes: u64,
63 pub max_parts: u32,
65 pub max_files: u32,
67 pub max_file_bytes: u64,
69 pub max_part_header_bytes: u32,
71 pub max_part_headers: u16,
73 pub max_filename_bytes: u32,
76 pub max_preamble_bytes: u32,
78 pub max_epilogue_bytes: u32,
80 pub max_padding_bytes: u8,
82 pub sniff_bytes: u32,
84 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
103const 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 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 with_max_body_bytes, max_body_bytes, u64);
143 setter!(
144 with_max_fields, max_fields, u32);
146 setter!(
147 with_max_name_bytes, max_name_bytes, u32);
149 setter!(
150 with_max_value_bytes, max_value_bytes, u32);
152 setter!(
153 with_max_text_bytes, max_text_bytes, u64);
155 setter!(
156 with_max_parts, max_parts, u32);
158 setter!(
159 with_max_files, max_files, u32);
161 setter!(
162 with_max_file_bytes, max_file_bytes, u64);
164 setter!(
165 with_max_part_header_bytes, max_part_header_bytes, u32);
167 setter!(
168 with_max_part_headers, max_part_headers, u16);
170 setter!(
171 with_max_filename_bytes, max_filename_bytes, u32);
173 setter!(
174 with_max_preamble_bytes, max_preamble_bytes, u32);
176 setter!(
177 with_max_epilogue_bytes, max_epilogue_bytes, u32);
179 setter!(
180 with_max_padding_bytes, max_padding_bytes, u8);
182 setter!(
183 with_sniff_bytes, sniff_bytes, u32);
185 setter!(
186 with_max_token_bytes, max_token_bytes, u16);
188
189 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 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 #[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 #[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 pub(crate) const fn body(&self) -> u64 {
301 self.max_body_bytes
302 }
303
304 pub(crate) const fn text(&self) -> u64 {
305 min_u64(self.max_text_bytes, self.body())
306 }
307
308 pub(crate) const fn value(&self) -> u32 {
309 min_u64(self.max_value_bytes as u64, self.text()) as u32
310 }
311
312 pub(crate) const fn name(&self) -> u32 {
313 min_u64(self.max_name_bytes as u64, self.body()) as u32
314 }
315
316 pub(crate) const fn file(&self) -> u64 {
317 min_u64(self.max_file_bytes, self.body())
318 }
319
320 pub(crate) const fn filename(&self) -> u32 {
321 min_u64(
322 self.max_filename_bytes as u64,
323 self.max_part_header_bytes as u64,
324 ) as u32
325 }
326
327 pub(crate) const fn token(&self) -> u16 {
328 min_u64(self.max_token_bytes as u64, self.value() as u64) as u16
329 }
330
331 pub(crate) const fn preamble(&self) -> u64 {
332 min_u64(self.max_preamble_bytes as u64, self.body())
333 }
334
335 pub(crate) const fn epilogue(&self) -> u64 {
336 min_u64(self.max_epilogue_bytes as u64, self.body())
337 }
338
339 pub(crate) const fn body_usize(&self) -> usize {
340 to_usize(self.body())
341 }
342}
343
344impl Default for Limits {
345 fn default() -> Self {
346 Self::DEFAULT
347 }
348}
349
350#[non_exhaustive]
365#[derive(Clone, Copy, Debug, PartialEq, Eq)]
366pub struct Policy {
367 pub percent: PercentPolicy,
369 pub extended_filename: ExtendedFilenamePolicy,
371 pub unknown_headers: HeaderPolicy,
373 pub empty_files: EmptyFilePolicy,
375 pub token_field: Option<TokenField>,
377}
378
379impl Policy {
380 pub const DEFAULT: Policy = Policy {
382 percent: PercentPolicy::Refuse,
383 extended_filename: ExtendedFilenamePolicy::Refuse,
384 unknown_headers: HeaderPolicy::Ignore,
385 empty_files: EmptyFilePolicy::Report,
386 token_field: None,
387 };
388
389 #[must_use]
391 pub const fn with_percent(mut self, policy: PercentPolicy) -> Self {
392 self.percent = policy;
393 self
394 }
395
396 #[must_use]
398 pub const fn with_extended_filename(mut self, policy: ExtendedFilenamePolicy) -> Self {
399 self.extended_filename = policy;
400 self
401 }
402
403 #[must_use]
405 pub const fn with_unknown_headers(mut self, policy: HeaderPolicy) -> Self {
406 self.unknown_headers = policy;
407 self
408 }
409
410 #[must_use]
412 pub const fn with_empty_files(mut self, policy: EmptyFilePolicy) -> Self {
413 self.empty_files = policy;
414 self
415 }
416
417 #[must_use]
419 pub const fn with_token_field(mut self, field: TokenField) -> Self {
420 self.token_field = Some(field);
421 self
422 }
423}
424
425impl Default for Policy {
426 fn default() -> Self {
427 Self::DEFAULT
428 }
429}
430
431#[non_exhaustive]
444#[derive(Clone, Copy, Debug, PartialEq, Eq)]
445pub enum PercentPolicy {
446 Refuse,
448 Literal,
450}
451
452#[non_exhaustive]
468#[derive(Clone, Copy, Debug, PartialEq, Eq)]
469pub enum ExtendedFilenamePolicy {
470 Refuse,
472 AcceptAlone,
474 Ignore,
476}
477
478#[non_exhaustive]
489#[derive(Clone, Copy, Debug, PartialEq, Eq)]
490pub enum HeaderPolicy {
491 Ignore,
494 Refuse,
496}
497
498#[non_exhaustive]
508#[derive(Clone, Copy, Debug, PartialEq, Eq)]
509pub enum EmptyFilePolicy {
510 Report,
512 Skip,
514}
515
516#[derive(Clone, Copy, Debug, PartialEq, Eq)]
528pub struct TokenField {
529 name: &'static str,
530 placement: TokenPlacement,
531}
532
533impl TokenField {
534 #[must_use]
536 pub const fn new(name: &'static str) -> TokenField {
537 TokenField {
538 name,
539 placement: TokenPlacement::BeforeFirstFile,
540 }
541 }
542
543 #[must_use]
545 pub const fn anywhere(self) -> TokenField {
546 TokenField {
547 name: self.name,
548 placement: TokenPlacement::Anywhere,
549 }
550 }
551
552 #[must_use]
554 pub const fn name(&self) -> &'static str {
555 self.name
556 }
557
558 #[must_use]
560 pub const fn placement(&self) -> TokenPlacement {
561 self.placement
562 }
563}
564
565#[non_exhaustive]
567#[derive(Clone, Copy, Debug, PartialEq, Eq)]
568pub enum TokenPlacement {
569 BeforeFirstFile,
571 Anywhere,
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578
579 #[test]
580 fn test_limits_default_validates() {
581 assert!(Limits::DEFAULT.validate().is_ok());
582 assert_eq!(Limits::default(), Limits::DEFAULT);
583 }
584
585 #[test]
586 fn test_limits_header_budget_below_64_refused() {
587 for bytes in [0, 1, 63] {
588 let l = Limits::DEFAULT.with_max_part_header_bytes(bytes);
589 assert_eq!(
590 l.validate(),
591 Err(FormError::InvalidLimits {
592 limit: Limit::PartHeaderBytes
593 })
594 );
595 }
596 assert!(
597 Limits::DEFAULT
598 .with_max_part_header_bytes(64)
599 .validate()
600 .is_ok()
601 );
602 }
603
604 #[test]
605 fn test_limits_containing_limit_caps_contained() {
606 let l = Limits::DEFAULT.with_max_body_bytes(4096);
607 assert_eq!(l.text(), 4096);
608 assert_eq!(l.value(), 4096);
609 assert_eq!(l.file(), 4096);
610 assert_eq!(l.name(), 256);
611 assert_eq!(l.token(), 128);
612 let l = l.with_max_value_bytes(10);
613 assert_eq!((l.value(), l.token()), (10, 10));
614 let l = Limits::DEFAULT.with_max_part_header_bytes(100);
615 assert_eq!(l.filename(), 100);
616 let l = Limits::DEFAULT.with_sniff_bytes(64).with_max_file_bytes(16);
617 assert!(l.validate().is_ok());
618 }
619
620 #[test]
621 fn test_limits_zero_token_with_token_field_refused() {
622 let policy = Policy::DEFAULT.with_token_field(TokenField::new("t"));
623 let l = Limits::DEFAULT.with_max_token_bytes(0);
624 assert_eq!(
625 l.validate_with(&policy),
626 Err(FormError::InvalidLimits {
627 limit: Limit::TokenLength
628 })
629 );
630 assert!(l.validate_with(&Policy::DEFAULT).is_ok());
631 }
632
633 #[test]
634 fn test_buffer_lengths_follow_policy() {
635 let l = Limits::DEFAULT;
636 assert_eq!(l.multipart_buffer_len(&Policy::DEFAULT), 8192 + 256 + 32);
637 let p = Policy::DEFAULT
638 .with_extended_filename(ExtendedFilenamePolicy::AcceptAlone)
639 .with_token_field(TokenField::new("t"));
640 assert_eq!(l.multipart_buffer_len(&p), 8192 + 256 + 1024 + 128 + 32);
641 assert_eq!(l.urlencoded_buffer_len(), 256 + 1024 * 1024 + 1);
642 }
643
644 #[test]
645 fn test_setters_set_one_field_each() {
646 let l = Limits::DEFAULT
647 .with_max_body_bytes(1)
648 .with_max_fields(2)
649 .with_max_name_bytes(3)
650 .with_max_value_bytes(4)
651 .with_max_text_bytes(5)
652 .with_max_parts(6)
653 .with_max_files(7)
654 .with_max_file_bytes(8)
655 .with_max_part_header_bytes(9)
656 .with_max_part_headers(10)
657 .with_max_filename_bytes(11)
658 .with_max_preamble_bytes(12)
659 .with_max_epilogue_bytes(13)
660 .with_max_padding_bytes(14)
661 .with_sniff_bytes(15)
662 .with_max_token_bytes(16);
663 assert_eq!(
664 (
665 l.max_body_bytes,
666 l.max_fields,
667 l.max_name_bytes,
668 l.max_value_bytes
669 ),
670 (1, 2, 3, 4)
671 );
672 assert_eq!(
673 (l.max_text_bytes, l.max_parts, l.max_files, l.max_file_bytes),
674 (5, 6, 7, 8)
675 );
676 assert_eq!(
677 (
678 l.max_part_header_bytes,
679 l.max_part_headers,
680 l.max_filename_bytes,
681 l.max_preamble_bytes
682 ),
683 (9, 10, 11, 12)
684 );
685 assert_eq!(
686 (
687 l.max_epilogue_bytes,
688 l.max_padding_bytes,
689 l.sniff_bytes,
690 l.max_token_bytes
691 ),
692 (13, 14, 15, 16)
693 );
694 }
695
696 #[test]
697 fn test_policy_setters() {
698 let p = Policy::DEFAULT
699 .with_percent(PercentPolicy::Literal)
700 .with_extended_filename(ExtendedFilenamePolicy::Ignore)
701 .with_unknown_headers(HeaderPolicy::Refuse)
702 .with_empty_files(EmptyFilePolicy::Skip)
703 .with_token_field(TokenField::new("t").anywhere());
704 assert_eq!(p.percent, PercentPolicy::Literal);
705 assert_eq!(p.extended_filename, ExtendedFilenamePolicy::Ignore);
706 assert_eq!(p.unknown_headers, HeaderPolicy::Refuse);
707 assert_eq!(p.empty_files, EmptyFilePolicy::Skip);
708 assert_eq!(
709 p.token_field.map(|t| (t.name(), t.placement())),
710 Some(("t", TokenPlacement::Anywhere))
711 );
712 assert_eq!(Policy::default(), Policy::DEFAULT);
713 }
714}