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 #[cfg(feature = "alloc")]
321 pub(crate) const fn sniff(&self) -> u32 {
322 min_u64(self.sniff_bytes as u64, self.file()) as u32
323 }
324
325 pub(crate) const fn filename(&self) -> u32 {
326 min_u64(
327 self.max_filename_bytes as u64,
328 self.max_part_header_bytes as u64,
329 ) as u32
330 }
331
332 pub(crate) const fn token(&self) -> u16 {
333 min_u64(self.max_token_bytes as u64, self.value() as u64) as u16
334 }
335
336 pub(crate) const fn preamble(&self) -> u64 {
337 min_u64(self.max_preamble_bytes as u64, self.body())
338 }
339
340 pub(crate) const fn epilogue(&self) -> u64 {
341 min_u64(self.max_epilogue_bytes as u64, self.body())
342 }
343
344 pub(crate) const fn body_usize(&self) -> usize {
345 to_usize(self.body())
346 }
347}
348
349impl Default for Limits {
350 fn default() -> Self {
351 Self::DEFAULT
352 }
353}
354
355#[non_exhaustive]
370#[derive(Clone, Copy, Debug, PartialEq, Eq)]
371pub struct Policy {
372 pub percent: PercentPolicy,
374 pub extended_filename: ExtendedFilenamePolicy,
376 pub unknown_headers: HeaderPolicy,
378 pub empty_files: EmptyFilePolicy,
380 pub token_field: Option<TokenField>,
382}
383
384impl Policy {
385 pub const DEFAULT: Policy = Policy {
387 percent: PercentPolicy::Refuse,
388 extended_filename: ExtendedFilenamePolicy::Refuse,
389 unknown_headers: HeaderPolicy::Ignore,
390 empty_files: EmptyFilePolicy::Report,
391 token_field: None,
392 };
393
394 #[must_use]
396 pub const fn with_percent(mut self, policy: PercentPolicy) -> Self {
397 self.percent = policy;
398 self
399 }
400
401 #[must_use]
403 pub const fn with_extended_filename(mut self, policy: ExtendedFilenamePolicy) -> Self {
404 self.extended_filename = policy;
405 self
406 }
407
408 #[must_use]
410 pub const fn with_unknown_headers(mut self, policy: HeaderPolicy) -> Self {
411 self.unknown_headers = policy;
412 self
413 }
414
415 #[must_use]
417 pub const fn with_empty_files(mut self, policy: EmptyFilePolicy) -> Self {
418 self.empty_files = policy;
419 self
420 }
421
422 #[must_use]
424 pub const fn with_token_field(mut self, field: TokenField) -> Self {
425 self.token_field = Some(field);
426 self
427 }
428}
429
430impl Default for Policy {
431 fn default() -> Self {
432 Self::DEFAULT
433 }
434}
435
436#[non_exhaustive]
449#[derive(Clone, Copy, Debug, PartialEq, Eq)]
450pub enum PercentPolicy {
451 Refuse,
453 Literal,
455}
456
457#[non_exhaustive]
473#[derive(Clone, Copy, Debug, PartialEq, Eq)]
474pub enum ExtendedFilenamePolicy {
475 Refuse,
477 AcceptAlone,
479 Ignore,
481}
482
483#[non_exhaustive]
494#[derive(Clone, Copy, Debug, PartialEq, Eq)]
495pub enum HeaderPolicy {
496 Ignore,
499 Refuse,
501}
502
503#[non_exhaustive]
513#[derive(Clone, Copy, Debug, PartialEq, Eq)]
514pub enum EmptyFilePolicy {
515 Report,
517 Skip,
519}
520
521#[derive(Clone, Copy, Debug, PartialEq, Eq)]
533pub struct TokenField {
534 name: &'static str,
535 placement: TokenPlacement,
536}
537
538impl TokenField {
539 #[must_use]
541 pub const fn new(name: &'static str) -> TokenField {
542 TokenField {
543 name,
544 placement: TokenPlacement::BeforeFirstFile,
545 }
546 }
547
548 #[must_use]
550 pub const fn anywhere(self) -> TokenField {
551 TokenField {
552 name: self.name,
553 placement: TokenPlacement::Anywhere,
554 }
555 }
556
557 #[must_use]
559 pub const fn name(&self) -> &'static str {
560 self.name
561 }
562
563 #[must_use]
565 pub const fn placement(&self) -> TokenPlacement {
566 self.placement
567 }
568}
569
570#[non_exhaustive]
572#[derive(Clone, Copy, Debug, PartialEq, Eq)]
573pub enum TokenPlacement {
574 BeforeFirstFile,
576 Anywhere,
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn test_limits_default_validates() {
586 assert!(Limits::DEFAULT.validate().is_ok());
587 assert_eq!(Limits::default(), Limits::DEFAULT);
588 }
589
590 #[test]
591 fn test_limits_header_budget_below_64_refused() {
592 for bytes in [0, 1, 63] {
593 let l = Limits::DEFAULT.with_max_part_header_bytes(bytes);
594 assert_eq!(
595 l.validate(),
596 Err(FormError::InvalidLimits {
597 limit: Limit::PartHeaderBytes
598 })
599 );
600 }
601 assert!(
602 Limits::DEFAULT
603 .with_max_part_header_bytes(64)
604 .validate()
605 .is_ok()
606 );
607 }
608
609 #[test]
610 fn test_limits_containing_limit_caps_contained() {
611 let l = Limits::DEFAULT.with_max_body_bytes(4096);
612 assert_eq!(l.text(), 4096);
613 assert_eq!(l.value(), 4096);
614 assert_eq!(l.file(), 4096);
615 assert_eq!(l.name(), 256);
616 assert_eq!(l.token(), 128);
617 let l = l.with_max_value_bytes(10);
618 assert_eq!((l.value(), l.token()), (10, 10));
619 let l = Limits::DEFAULT.with_max_part_header_bytes(100);
620 assert_eq!(l.filename(), 100);
621 let l = Limits::DEFAULT.with_sniff_bytes(64).with_max_file_bytes(16);
622 assert!(l.validate().is_ok());
623 }
624
625 #[test]
626 fn test_limits_zero_token_with_token_field_refused() {
627 let policy = Policy::DEFAULT.with_token_field(TokenField::new("t"));
628 let l = Limits::DEFAULT.with_max_token_bytes(0);
629 assert_eq!(
630 l.validate_with(&policy),
631 Err(FormError::InvalidLimits {
632 limit: Limit::TokenLength
633 })
634 );
635 assert!(l.validate_with(&Policy::DEFAULT).is_ok());
636 }
637
638 #[test]
639 fn test_buffer_lengths_follow_policy() {
640 let l = Limits::DEFAULT;
641 assert_eq!(l.multipart_buffer_len(&Policy::DEFAULT), 8192 + 256 + 32);
642 let p = Policy::DEFAULT
643 .with_extended_filename(ExtendedFilenamePolicy::AcceptAlone)
644 .with_token_field(TokenField::new("t"));
645 assert_eq!(l.multipart_buffer_len(&p), 8192 + 256 + 1024 + 128 + 32);
646 assert_eq!(l.urlencoded_buffer_len(), 256 + 1024 * 1024 + 1);
647 }
648
649 #[test]
650 fn test_setters_set_one_field_each() {
651 let l = Limits::DEFAULT
652 .with_max_body_bytes(1)
653 .with_max_fields(2)
654 .with_max_name_bytes(3)
655 .with_max_value_bytes(4)
656 .with_max_text_bytes(5)
657 .with_max_parts(6)
658 .with_max_files(7)
659 .with_max_file_bytes(8)
660 .with_max_part_header_bytes(9)
661 .with_max_part_headers(10)
662 .with_max_filename_bytes(11)
663 .with_max_preamble_bytes(12)
664 .with_max_epilogue_bytes(13)
665 .with_max_padding_bytes(14)
666 .with_sniff_bytes(15)
667 .with_max_token_bytes(16);
668 assert_eq!(
669 (
670 l.max_body_bytes,
671 l.max_fields,
672 l.max_name_bytes,
673 l.max_value_bytes
674 ),
675 (1, 2, 3, 4)
676 );
677 assert_eq!(
678 (l.max_text_bytes, l.max_parts, l.max_files, l.max_file_bytes),
679 (5, 6, 7, 8)
680 );
681 assert_eq!(
682 (
683 l.max_part_header_bytes,
684 l.max_part_headers,
685 l.max_filename_bytes,
686 l.max_preamble_bytes
687 ),
688 (9, 10, 11, 12)
689 );
690 assert_eq!(
691 (
692 l.max_epilogue_bytes,
693 l.max_padding_bytes,
694 l.sniff_bytes,
695 l.max_token_bytes
696 ),
697 (13, 14, 15, 16)
698 );
699 }
700
701 #[test]
702 fn test_policy_setters() {
703 let p = Policy::DEFAULT
704 .with_percent(PercentPolicy::Literal)
705 .with_extended_filename(ExtendedFilenamePolicy::Ignore)
706 .with_unknown_headers(HeaderPolicy::Refuse)
707 .with_empty_files(EmptyFilePolicy::Skip)
708 .with_token_field(TokenField::new("t").anywhere());
709 assert_eq!(p.percent, PercentPolicy::Literal);
710 assert_eq!(p.extended_filename, ExtendedFilenamePolicy::Ignore);
711 assert_eq!(p.unknown_headers, HeaderPolicy::Refuse);
712 assert_eq!(p.empty_files, EmptyFilePolicy::Skip);
713 assert_eq!(
714 p.token_field.map(|t| (t.name(), t.placement())),
715 Some(("t", TokenPlacement::Anywhere))
716 );
717 assert_eq!(Policy::default(), Policy::DEFAULT);
718 }
719}