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    // Effective values: each capped by the limit that contains it.
299
300    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/// The behaviours a route may choose. The defaults are the strict ones;
356/// every alternative is named after what it allows.
357///
358/// # Examples
359///
360/// ```
361/// use form_net::{EmptyFilePolicy, HeaderPolicy, Policy, TokenField};
362///
363/// const UPLOADS: Policy = Policy::DEFAULT
364///     .with_token_field(TokenField::new("csrf_token"))
365///     .with_empty_files(EmptyFilePolicy::Skip)
366///     .with_unknown_headers(HeaderPolicy::Refuse);
367/// assert_eq!(UPLOADS.token_field.map(|t| t.name()), Some("csrf_token"));
368/// ```
369#[non_exhaustive]
370#[derive(Clone, Copy, Debug, PartialEq, Eq)]
371pub struct Policy {
372    /// A malformed `%` in urlencoded input. Default `Refuse`.
373    pub percent: PercentPolicy,
374    /// `filename*` in a part. Default `Refuse`.
375    pub extended_filename: ExtendedFilenamePolicy,
376    /// Part headers other than the three known ones. Default `Ignore`.
377    pub unknown_headers: HeaderPolicy,
378    /// A file input left empty. Default `Report`.
379    pub empty_files: EmptyFilePolicy,
380    /// The anti-forgery token field to capture. Default none.
381    pub token_field: Option<TokenField>,
382}
383
384impl Policy {
385    /// The strict defaults.
386    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    /// Sets `percent`.
395    #[must_use]
396    pub const fn with_percent(mut self, policy: PercentPolicy) -> Self {
397        self.percent = policy;
398        self
399    }
400
401    /// Sets `extended_filename`.
402    #[must_use]
403    pub const fn with_extended_filename(mut self, policy: ExtendedFilenamePolicy) -> Self {
404        self.extended_filename = policy;
405        self
406    }
407
408    /// Sets `unknown_headers`.
409    #[must_use]
410    pub const fn with_unknown_headers(mut self, policy: HeaderPolicy) -> Self {
411        self.unknown_headers = policy;
412        self
413    }
414
415    /// Sets `empty_files`.
416    #[must_use]
417    pub const fn with_empty_files(mut self, policy: EmptyFilePolicy) -> Self {
418        self.empty_files = policy;
419        self
420    }
421
422    /// Sets the token field to capture.
423    #[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/// What a `%` not followed by two hex digits means in urlencoded input.
437///
438/// # Examples
439///
440/// ```
441/// use form_net::{decode_into, PercentPolicy};
442///
443/// let mut out = [0u8; 8];
444/// assert!(decode_into(b"100%", &mut out, PercentPolicy::Refuse).is_err());
445/// assert_eq!(decode_into(b"100%", &mut out, PercentPolicy::Literal)?, "100%");
446/// # Ok::<(), form_net::FormError>(())
447/// ```
448#[non_exhaustive]
449#[derive(Clone, Copy, Debug, PartialEq, Eq)]
450pub enum PercentPolicy {
451    /// Refuse it (default).
452    Refuse,
453    /// Keep it as a literal byte, as WHATWG percent-decode does.
454    Literal,
455}
456
457/// What a `filename*` parameter (RFC 8187) in a part means. Browsers never
458/// send it; RFC 7578 forbids senders from using it.
459///
460/// Both `filename` and `filename*` in one part are refused under every
461/// policy.
462///
463/// # Examples
464///
465/// ```
466/// use form_net::{ExtendedFilenamePolicy, Policy};
467///
468/// let accept = Policy::DEFAULT.with_extended_filename(ExtendedFilenamePolicy::AcceptAlone);
469/// assert_eq!(Policy::DEFAULT.extended_filename, ExtendedFilenamePolicy::Refuse);
470/// assert_ne!(accept, Policy::DEFAULT);
471/// ```
472#[non_exhaustive]
473#[derive(Clone, Copy, Debug, PartialEq, Eq)]
474pub enum ExtendedFilenamePolicy {
475    /// Refuse any `filename*` (default).
476    Refuse,
477    /// Accept and decode it (UTF-8 only) when it is the only file name.
478    AcceptAlone,
479    /// Drop it; a part with only `filename*` is then a text field.
480    Ignore,
481}
482
483/// Part headers other than `Content-Disposition`, `Content-Type`, and
484/// `Content-Transfer-Encoding`.
485///
486/// # Examples
487///
488/// ```
489/// use form_net::{HeaderPolicy, Policy};
490///
491/// assert_eq!(Policy::DEFAULT.unknown_headers, HeaderPolicy::Ignore);
492/// ```
493#[non_exhaustive]
494#[derive(Clone, Copy, Debug, PartialEq, Eq)]
495pub enum HeaderPolicy {
496    /// Skip them, as RFC 7578 section 4.8 requires (default). They still
497    /// count toward the header budgets.
498    Ignore,
499    /// Refuse them.
500    Refuse,
501}
502
503/// A file input left empty (`filename=""` and no data).
504///
505/// # Examples
506///
507/// ```
508/// use form_net::{EmptyFilePolicy, Policy};
509///
510/// assert_eq!(Policy::DEFAULT.empty_files, EmptyFilePolicy::Report);
511/// ```
512#[non_exhaustive]
513#[derive(Clone, Copy, Debug, PartialEq, Eq)]
514pub enum EmptyFilePolicy {
515    /// Report it as a file part with an empty file name (default).
516    Report,
517    /// Produce nothing for it; it does not count against `max_files`.
518    Skip,
519}
520
521/// The anti-forgery token field to capture from a multipart body.
522///
523/// # Examples
524///
525/// ```
526/// use form_net::{TokenField, TokenPlacement};
527///
528/// let strict = TokenField::new("csrf_token");
529/// assert_eq!(strict.placement(), TokenPlacement::BeforeFirstFile);
530/// assert_eq!(strict.anywhere().placement(), TokenPlacement::Anywhere);
531/// ```
532#[derive(Clone, Copy, Debug, PartialEq, Eq)]
533pub struct TokenField {
534    name: &'static str,
535    placement: TokenPlacement,
536}
537
538impl TokenField {
539    /// A token field named `name` that must come before the first file.
540    #[must_use]
541    pub const fn new(name: &'static str) -> TokenField {
542        TokenField {
543            name,
544            placement: TokenPlacement::BeforeFirstFile,
545        }
546    }
547
548    /// The same field, allowed anywhere in the body.
549    #[must_use]
550    pub const fn anywhere(self) -> TokenField {
551        TokenField {
552            name: self.name,
553            placement: TokenPlacement::Anywhere,
554        }
555    }
556
557    /// The field name, matched byte for byte.
558    #[must_use]
559    pub const fn name(&self) -> &'static str {
560        self.name
561    }
562
563    /// Where the field must be.
564    #[must_use]
565    pub const fn placement(&self) -> TokenPlacement {
566        self.placement
567    }
568}
569
570/// Where the token field must be.
571#[non_exhaustive]
572#[derive(Clone, Copy, Debug, PartialEq, Eq)]
573pub enum TokenPlacement {
574    /// Before the first file part (default of [`TokenField::new`]).
575    BeforeFirstFile,
576    /// Anywhere.
577    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}