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