Skip to main content

form_net/multipart/
parser.rs

1//! The sans-IO multipart parser: a state machine over the delimiter matcher
2//! and the header reader.
3
4use core::fmt;
5
6use crate::error::{EncodingError, FormError, Limit, Location, PolicyRefusal, SyntaxError};
7use crate::limits::{CHARSET_CAPTURE, EmptyFilePolicy, Limits, Policy, TokenPlacement};
8use crate::multipart::boundary::Boundary;
9use crate::multipart::filename::FileName;
10use crate::multipart::header::{Areas, BlockScan, HeadFacts, Text, read_block};
11
12/// Whether a part is a text field or a file.
13///
14/// # Examples
15///
16/// ```
17/// # let body = b"--X\r\nContent-Disposition: form-data; name=a\r\n\r\n1\r\n--X--";
18/// let mp = form_net::parse_multipart(b"multipart/form-data; boundary=X", body)?;
19/// assert_eq!(mp.parts()[0].kind(), form_net::PartKind::Field);
20/// # Ok::<(), form_net::FormError>(())
21/// ```
22#[non_exhaustive]
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum PartKind {
25    /// A text field: no `filename` parameter.
26    Field,
27    /// A file: a `filename` parameter (even an empty one) or an accepted
28    /// `filename*`.
29    File,
30}
31
32/// The `charset` a part declared.
33///
34/// `Unspecified` means no `charset` parameter: UTF-8 is assumed unless a
35/// `_charset_` field before the part said otherwise. `Other` is any label
36/// that is not UTF-8 or US-ASCII; its text is refused by every text accessor.
37///
38/// # Examples
39///
40/// ```
41/// use form_net::Charset;
42///
43/// # let body = b"--X\r\nContent-Disposition: form-data; name=a\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n1\r\n--X--";
44/// # let mp = form_net::parse_multipart(b"multipart/form-data; boundary=X", body)?;
45/// assert_eq!(mp.parts()[0].content_type().unwrap().charset(), Charset::Utf8);
46/// # Ok::<(), form_net::FormError>(())
47/// ```
48#[non_exhaustive]
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum Charset {
51    /// `utf-8`.
52    Utf8,
53    /// `us-ascii`.
54    Ascii,
55    /// No `charset` parameter.
56    Unspecified,
57    /// Any other label.
58    Other,
59}
60
61impl Charset {
62    pub(crate) fn from_label(label: &[u8]) -> Charset {
63        if label.eq_ignore_ascii_case(b"utf-8") {
64            Charset::Utf8
65        } else if label.eq_ignore_ascii_case(b"us-ascii") {
66            Charset::Ascii
67        } else {
68            Charset::Other
69        }
70    }
71}
72
73/// One step of the parser: how many input bytes it consumed, and what
74/// happened.
75#[non_exhaustive]
76#[derive(Debug)]
77pub struct Step<'p> {
78    /// Bytes of the input consumed; pass the rest (and later new bytes) to
79    /// the next call.
80    pub consumed: usize,
81    /// What happened.
82    pub event: Event<'p>,
83}
84
85/// What the parser found.
86///
87/// Per part the order is always `PartStart`, any number of `Data` (none for
88/// an empty part), `PartEnd`. A `Data` slice is never empty. After the last
89/// part comes `End`. `NeedInput` can appear anywhere between.
90///
91/// `Debug` prints the length of `Data`, never the bytes.
92#[non_exhaustive]
93pub enum Event<'p> {
94    /// A part's headers were read and checked.
95    PartStart(PartHead<'p>),
96    /// Bytes of the current part's data.
97    Data(&'p [u8]),
98    /// The current part ended.
99    PartEnd(PartSummary),
100    /// The close delimiter was read: the form is complete.
101    End,
102    /// Every byte given was consumed; give more.
103    NeedInput,
104}
105
106impl fmt::Debug for Event<'_> {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match self {
109            Self::PartStart(head) => f.debug_tuple("PartStart").field(head).finish(),
110            Self::Data(bytes) => write!(f, "Data({} bytes)", bytes.len()),
111            Self::PartEnd(summary) => f.debug_tuple("PartEnd").field(summary).finish(),
112            Self::End => f.write_str("End"),
113            Self::NeedInput => f.write_str("NeedInput"),
114        }
115    }
116}
117
118/// What one part was.
119#[non_exhaustive]
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121pub struct PartSummary {
122    /// The part index, from 0.
123    pub index: u32,
124    /// Field or file.
125    pub kind: PartKind,
126    /// The length of the part's data.
127    pub bytes: u64,
128    /// Whether the part was skipped (by `skip_part` or an inspector).
129    pub skipped: bool,
130}
131
132/// The validated header block of one part, borrowed.
133///
134/// A head is only produced after every header budget and rule passed.
135#[derive(Clone, Copy)]
136pub struct PartHead<'p> {
137    index: u32,
138    block: &'p [u8],
139    name: &'p str,
140    name_raw: &'p [u8],
141    kind: PartKind,
142    filename: Option<FileName<'p>>,
143    content_type: Option<&'p [u8]>,
144    charset: Charset,
145}
146
147impl fmt::Debug for PartHead<'_> {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        f.debug_struct("PartHead")
150            .field("index", &self.index)
151            .field("name", &self.name)
152            .field("kind", &self.kind)
153            .field("filename", &self.filename)
154            .finish_non_exhaustive()
155    }
156}
157
158impl<'p> PartHead<'p> {
159    /// The part index, from 0, in body order.
160    #[must_use]
161    pub const fn index(&self) -> u32 {
162        self.index
163    }
164
165    /// The field name, with `%0A`, `%0D`, `%22` decoded to LF, CR, `"` (no
166    /// other escape is decoded). Always valid UTF-8 and never empty.
167    #[must_use]
168    pub const fn name(&self) -> &'p str {
169        self.name
170    }
171
172    /// The field name as sent.
173    #[must_use]
174    pub const fn name_raw(&self) -> &'p [u8] {
175        self.name_raw
176    }
177
178    /// Field or file.
179    #[must_use]
180    pub const fn kind(&self) -> PartKind {
181        self.kind
182    }
183
184    /// Whether this is a file part.
185    #[must_use]
186    pub const fn is_file(&self) -> bool {
187        matches!(self.kind, PartKind::File)
188    }
189
190    /// The client's file name, for file parts.
191    #[must_use]
192    pub const fn filename(&self) -> Option<FileName<'p>> {
193        self.filename
194    }
195
196    /// The declared `Content-Type`: a hint, never trusted.
197    #[must_use]
198    pub fn content_type(&self) -> Option<ContentType<'p>> {
199        self.content_type.and_then(ContentType::parse)
200    }
201
202    /// The part's own `charset` parameter (`Unspecified` when absent).
203    #[must_use]
204    pub const fn charset(&self) -> Charset {
205        self.charset
206    }
207
208    /// Every header line, in order, as raw `(name, value)` bytes.
209    #[must_use]
210    pub fn headers(&self) -> Headers<'p> {
211        Headers { rest: self.block }
212    }
213
214    /// The size of the header block, the final CRLF CRLF included.
215    #[must_use]
216    pub const fn header_bytes(&self) -> u32 {
217        self.block.len() as u32
218    }
219}
220
221/// A part's declared `Content-Type`, already checked. **A hint**: the client
222/// chose it.
223///
224/// # Examples
225///
226/// ```
227/// # let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=a.png\r\nContent-Type: Image/PNG; q=\"x y\"\r\n\r\n\r\n--X--";
228/// # let mp = form_net::parse_multipart(b"multipart/form-data; boundary=X", body)?;
229/// let ct = mp.file("f").unwrap().content_type().unwrap();
230/// assert!(ct.is("image", "png"));
231/// assert_eq!((ct.type_(), ct.subtype()), ("Image", "PNG"));
232/// assert_eq!(ct.parameter("Q"), Some(&b"x y"[..]));
233/// # Ok::<(), form_net::FormError>(())
234/// ```
235#[derive(Clone, Copy)]
236pub struct ContentType<'p> {
237    raw: &'p [u8],
238    media: parse_net::MediaType<'p>,
239}
240
241impl fmt::Debug for ContentType<'_> {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        f.debug_struct("ContentType")
244            .field("type", &self.type_())
245            .field("subtype", &self.subtype())
246            .finish_non_exhaustive()
247    }
248}
249
250impl<'p> ContentType<'p> {
251    pub(crate) fn parse(raw: &'p [u8]) -> Option<ContentType<'p>> {
252        parse_net::media::parse(raw, &crate::kind::HEADER_LIMITS)
253            .ok()
254            .map(|media| ContentType { raw, media })
255    }
256
257    /// The value as sent.
258    #[must_use]
259    pub const fn raw(&self) -> &'p [u8] {
260        self.raw
261    }
262
263    /// The type, as sent.
264    #[must_use]
265    pub fn type_(&self) -> &'p str {
266        core::str::from_utf8(self.media.type_()).unwrap_or("")
267    }
268
269    /// The subtype, as sent.
270    #[must_use]
271    pub fn subtype(&self) -> &'p str {
272        core::str::from_utf8(self.media.subtype()).unwrap_or("")
273    }
274
275    /// Whether the type and subtype are these, ASCII case-insensitively.
276    #[must_use]
277    pub fn is(&self, type_: &str, subtype: &str) -> bool {
278        self.media.is(type_, subtype)
279    }
280
281    /// A parameter's value by name (ASCII case-insensitive), without
282    /// surrounding quotes; backslashes are literal, as browsers send them.
283    #[must_use]
284    pub fn parameter(&self, name: &str) -> Option<&'p [u8]> {
285        self.media
286            .params()
287            .get(name)
288            .map(|w| crate::kind::word_raw(&w))
289    }
290
291    /// The `charset` parameter.
292    #[must_use]
293    pub fn charset(&self) -> Charset {
294        self.parameter("charset")
295            .map_or(Charset::Unspecified, Charset::from_label)
296    }
297}
298
299/// Every header line of a part, in order, as raw `(name, value)` bytes with
300/// the value trimmed.
301///
302/// `Debug` prints the header names only: values carry file names.
303#[derive(Clone)]
304pub struct Headers<'p> {
305    rest: &'p [u8],
306}
307
308impl fmt::Debug for Headers<'_> {
309    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310        let mut list = f.debug_list();
311        for (name, _) in self.clone() {
312            list.entry(&core::str::from_utf8(name).unwrap_or("?"));
313        }
314        list.finish()
315    }
316}
317
318impl<'p> Iterator for Headers<'p> {
319    type Item = (&'p [u8], &'p [u8]);
320
321    fn next(&mut self) -> Option<Self::Item> {
322        let lf = self.rest.iter().position(|&b| b == b'\n')?;
323        let line = self.rest.get(..lf.saturating_sub(1)).unwrap_or(&[]);
324        self.rest = self.rest.get(lf.wrapping_add(1)..).unwrap_or(&[]);
325        if line.is_empty() {
326            self.rest = &[];
327            return None;
328        }
329        let colon = line.iter().position(|&b| b == b':').unwrap_or(line.len());
330        let name = line.get(..colon).unwrap_or(&[]);
331        let value = line.get(colon.wrapping_add(1)..).unwrap_or(&[]);
332        Some((name, parse_net::text::trim_ows(value)))
333    }
334}
335
336#[derive(Clone, Copy, Debug, PartialEq, Eq)]
337enum State {
338    Preamble,
339    /// After a delimiter: expecting `--`, padding, or CRLF.
340    AfterDelimiter(After),
341    Headers,
342    Body,
343    /// The delimiter that ends the current part was found; `PartEnd` is due.
344    EndOfPart,
345    Epilogue,
346    Poisoned,
347}
348
349#[derive(Clone, Copy, Debug, PartialEq, Eq)]
350enum After {
351    Start,
352    Dash,
353    Padding(u8),
354    Cr,
355}
356
357/// The token field's progress.
358#[derive(Clone, Copy, Debug, PartialEq, Eq)]
359enum Token {
360    NotSeen,
361    Capturing,
362    Captured,
363}
364
365const SKIPPING: u8 = 1;
366const IS_TOKEN: u8 = 2;
367const IS_CHARSET: u8 = 4;
368const DEFERRED: u8 = 8;
369
370/// Where a head's block is.
371#[derive(Clone, Copy, Debug)]
372pub(crate) enum Source {
373    /// In the input, from this index.
374    Input(usize),
375    /// In the parser buffer, from 0.
376    Buffer,
377}
378
379/// What a call to `advance` produced, as positions only.
380#[derive(Clone, Copy, Debug)]
381pub(crate) enum Outcome {
382    PartStart(HeadFacts, Source),
383    /// `input[start..end]`.
384    DataInput(usize, usize),
385    /// The first bytes of the delimiter.
386    DataDelimiter(usize),
387    PartEnd(PartSummary),
388    End,
389    NeedInput,
390}
391
392/// The sans-IO `multipart/form-data` parser.
393///
394/// Feed it bytes as they arrive, in chunks of any size split at any byte;
395/// it returns one [`Event`] per call and how many bytes it consumed. The
396/// events are identical for every split of the same body. It never
397/// allocates after construction and never rescans a byte: the delimiter
398/// search is a streaming Knuth-Morris-Pratt automaton with an eight-byte skip
399/// to the next CR.
400///
401/// The buffer holds the header block, the decoded field name, the decoded
402/// `filename*` (under `AcceptAlone`), the token, and the `_charset_` value:
403/// [`Limits::multipart_buffer_len`] bytes. It is wiped on `reset` and on
404/// drop.
405///
406/// # Examples
407///
408/// ```
409/// use form_net::{Event, FormKind, Limits, MultipartParser, Policy};
410///
411/// let content_type = b"multipart/form-data; boundary=X";
412/// let body = b"--X\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\nHoliday\r\n--X--\r\n";
413/// let FormKind::Multipart(boundary) = FormKind::detect(content_type)? else { unreachable!() };
414/// let mut parser = MultipartParser::new(boundary, &Limits::DEFAULT, &Policy::DEFAULT)?;
415///
416/// let mut events = Vec::new();
417/// let mut chunks = body.chunks(7).peekable();     // any split gives the same events
418/// while let Some(chunk) = chunks.next() {
419///     let last = chunks.peek().is_none();
420///     let mut rest = chunk;
421///     loop {
422///         let step = parser.parse(rest, last)?;
423///         rest = &rest[step.consumed..];
424///         match step.event {
425///             Event::PartStart(head) => events.push(format!("start {}", head.name())),
426///             Event::Data(bytes) => events.push(format!("data {}", bytes.len())),
427///             Event::PartEnd(summary) => events.push(format!("end {}", summary.bytes)),
428///             Event::End => break,
429///             Event::NeedInput => break,
430///             _ => {}
431///         }
432///     }
433/// }
434/// assert!(parser.is_done());
435/// assert_eq!(events.first().map(String::as_str), Some("start title"));
436/// assert_eq!(events.last().map(String::as_str), Some("end 7"));
437/// # Ok::<(), form_net::FormError>(())
438/// ```
439pub struct MultipartParser<B: AsMut<[u8]> + AsRef<[u8]>> {
440    boundary: Boundary,
441    limits: Limits,
442    policy: Policy,
443    buffer: B,
444    state: State,
445    /// Delimiter bytes matched and not yet known to be data.
446    k: u8,
447    flags: u8,
448    token: Token,
449    token_len: u16,
450    charset_len: u8,
451    charset_done: bool,
452    scan: BlockScan,
453    /// Header bytes copied into the buffer.
454    copied: u32,
455    parts: u32,
456    files: u32,
457    fields: u32,
458    kind: PartKind,
459    part_bytes: u64,
460    text_total: u64,
461    data_start: u64,
462    preamble: u64,
463    epilogue: u64,
464    /// Where the current header block starts in the body.
465    head_start: u64,
466    /// Where the epilogue starts in the body.
467    epilogue_start: u64,
468    bytes_seen: u64,
469}
470
471impl<B: AsMut<[u8]> + AsRef<[u8]>> fmt::Debug for MultipartParser<B> {
472    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473        f.debug_struct("MultipartParser")
474            .field("boundary", &self.boundary)
475            .field("state", &self.state)
476            .field("bytes_seen", &self.bytes_seen)
477            .field("parts_seen", &self.parts)
478            .finish_non_exhaustive()
479    }
480}
481
482#[cfg(feature = "alloc")]
483impl MultipartParser<alloc::vec::Vec<u8>> {
484    /// A parser with its own buffer of
485    /// [`Limits::multipart_buffer_len`] bytes, allocated once.
486    ///
487    /// # Errors
488    ///
489    /// `InvalidLimits` from validating the limits with the policy.
490    ///
491    /// # Examples
492    ///
493    /// ```
494    /// use form_net::{Boundary, Limits, MultipartParser, Policy};
495    ///
496    /// let parser = MultipartParser::new(Boundary::new(b"X")?, &Limits::DEFAULT, &Policy::DEFAULT)?;
497    /// assert_eq!(parser.bytes_seen(), 0);
498    /// # Ok::<(), form_net::FormError>(())
499    /// ```
500    pub fn new(boundary: Boundary, limits: &Limits, policy: &Policy) -> Result<Self, FormError> {
501        limits.validate_with(policy)?;
502        let buffer = alloc::vec![0u8; limits.multipart_buffer_len(policy)];
503        Self::with_buffer(boundary, limits, policy, buffer)
504    }
505}
506
507impl<B: AsMut<[u8]> + AsRef<[u8]>> MultipartParser<B> {
508    /// A parser over a buffer the caller provides; it never allocates.
509    ///
510    /// # Errors
511    ///
512    /// `InvalidLimits`; `BufferTooSmall` when `buffer` is shorter than
513    /// [`Limits::multipart_buffer_len`].
514    ///
515    /// # Examples
516    ///
517    /// ```
518    /// use form_net::{Boundary, Limits, MultipartParser, Policy};
519    ///
520    /// const LIMITS: Limits = Limits::DEFAULT.with_max_part_header_bytes(1024);
521    /// const LEN: usize = LIMITS.multipart_buffer_len(&Policy::DEFAULT);
522    /// let mut buffer = [0u8; LEN];
523    /// let parser = MultipartParser::with_buffer(Boundary::new(b"X")?, &LIMITS, &Policy::DEFAULT, &mut buffer[..])?;
524    /// # Ok::<(), form_net::FormError>(())
525    /// ```
526    pub fn with_buffer(
527        boundary: Boundary,
528        limits: &Limits,
529        policy: &Policy,
530        buffer: B,
531    ) -> Result<Self, FormError> {
532        limits.validate_with(policy)?;
533        let needed = limits.multipart_buffer_len(policy);
534        if buffer.as_ref().len() < needed {
535            return Err(FormError::BufferTooSmall { needed });
536        }
537        Ok(Self {
538            boundary,
539            limits: *limits,
540            policy: *policy,
541            buffer,
542            state: State::Preamble,
543            // The body may start with `--boundary`: start as if CRLF had
544            // been seen.
545            k: 2,
546            flags: 0,
547            token: Token::NotSeen,
548            token_len: 0,
549            charset_len: 0,
550            charset_done: false,
551            scan: BlockScan::default(),
552            copied: 0,
553            parts: 0,
554            files: 0,
555            fields: 0,
556            kind: PartKind::Field,
557            part_bytes: 0,
558            text_total: 0,
559            data_start: 0,
560            preamble: 0,
561            epilogue: 0,
562            head_start: 0,
563            epilogue_start: 0,
564            bytes_seen: 0,
565        })
566    }
567
568    /// Consumes a prefix of `input` and returns one event. `eof = true` says
569    /// `input` ends the body.
570    ///
571    /// # Errors
572    ///
573    /// Every multipart error (see the crate's `docs/API.md`); `Poisoned`
574    /// after any error until [`reset`](Self::reset).
575    pub fn parse<'p>(&'p mut self, input: &'p [u8], eof: bool) -> Result<Step<'p>, FormError> {
576        let (consumed, outcome) = self.advance(input, eof)?;
577        Ok(Step {
578            consumed,
579            event: self.view(outcome, input),
580        })
581    }
582
583    /// Discards the rest of the current part: its bytes are consumed and
584    /// counted against the budgets but produce no `Data` events, and its
585    /// summary has `skipped = true`. Outside a part it does nothing.
586    pub fn skip_part(&mut self) {
587        if matches!(self.state, State::Body) {
588            self.flags |= SKIPPING;
589        }
590    }
591
592    /// Returns the parser to its initial state for a new body, wipes its
593    /// buffer, and clears the poisoned state. It never allocates.
594    pub fn reset(&mut self, boundary: Boundary) {
595        crate::wipe::wipe(self.buffer.as_mut());
596        let limits = self.limits;
597        let policy = self.policy;
598        self.boundary = boundary;
599        self.state = State::Preamble;
600        self.k = 2;
601        self.flags = 0;
602        self.token = Token::NotSeen;
603        self.token_len = 0;
604        self.charset_len = 0;
605        self.charset_done = false;
606        self.scan = BlockScan::default();
607        self.copied = 0;
608        self.parts = 0;
609        self.files = 0;
610        self.fields = 0;
611        self.kind = PartKind::Field;
612        self.part_bytes = 0;
613        self.text_total = 0;
614        self.data_start = 0;
615        self.preamble = 0;
616        self.epilogue = 0;
617        self.head_start = 0;
618        self.epilogue_start = 0;
619        self.bytes_seen = 0;
620        self.limits = limits;
621        self.policy = policy;
622    }
623
624    /// Body bytes consumed so far.
625    #[must_use]
626    pub const fn bytes_seen(&self) -> u64 {
627        self.bytes_seen
628    }
629
630    /// Parts started so far.
631    #[must_use]
632    pub const fn parts_seen(&self) -> u32 {
633        self.parts
634    }
635
636    /// File parts counted so far.
637    #[must_use]
638    pub const fn files_seen(&self) -> u32 {
639        self.files
640    }
641
642    /// Whether the close delimiter was read.
643    #[must_use]
644    pub fn is_done(&self) -> bool {
645        self.state == State::Epilogue
646    }
647
648    /// The limits the parser was built with.
649    #[must_use]
650    pub const fn limits(&self) -> &Limits {
651        &self.limits
652    }
653
654    /// The policy the parser was built with.
655    #[must_use]
656    pub const fn policy(&self) -> &Policy {
657        &self.policy
658    }
659
660    /// The token field's value, once its part has ended.
661    #[must_use]
662    pub fn captured_token(&self) -> Option<&str> {
663        if self.token != Token::Captured {
664            return None;
665        }
666        let start = self.token_area();
667        let bytes = self
668            .buffer
669            .as_ref()
670            .get(start..start.saturating_add(usize::from(self.token_len)))?;
671        core::str::from_utf8(bytes).ok()
672    }
673
674    /// The value of the last complete `_charset_` field.
675    #[must_use]
676    pub fn declared_charset(&self) -> Option<&[u8]> {
677        if !self.charset_done {
678            return None;
679        }
680        let start = self.charset_area();
681        self.buffer
682            .as_ref()
683            .get(start..start.saturating_add(usize::from(self.charset_len)))
684    }
685
686    /// The decoded name of the last head, `len` bytes in the name area.
687    #[cfg(feature = "alloc")]
688    pub(crate) fn area_name(&self, len: u32) -> &str {
689        let start = self.name_area();
690        let bytes = self
691            .buffer
692            .as_ref()
693            .get(start..start.saturating_add(len as usize))
694            .unwrap_or(&[]);
695        core::str::from_utf8(bytes).unwrap_or("")
696    }
697
698    /// The decoded `filename*` of the last head, `len` bytes.
699    #[cfg(feature = "alloc")]
700    pub(crate) fn area_filename(&self, len: u32) -> &[u8] {
701        let start = self.filename_area();
702        self.buffer
703            .as_ref()
704            .get(start..start.saturating_add(len as usize))
705            .unwrap_or(&[])
706    }
707
708    /// Where the current part's data starts in the body.
709    #[cfg(feature = "alloc")]
710    pub(crate) const fn data_start(&self) -> u64 {
711        self.data_start
712    }
713
714    // Buffer layout: header block, name, filename (AcceptAlone), token, charset.
715
716    fn name_area(&self) -> usize {
717        self.limits.max_part_header_bytes as usize
718    }
719
720    fn filename_area(&self) -> usize {
721        self.name_area().saturating_add(self.limits.name() as usize)
722    }
723
724    fn token_area(&self) -> usize {
725        let filename = if matches!(
726            self.policy.extended_filename,
727            crate::ExtendedFilenamePolicy::AcceptAlone
728        ) {
729            self.limits.filename() as usize
730        } else {
731            0
732        };
733        self.filename_area().saturating_add(filename)
734    }
735
736    fn charset_area(&self) -> usize {
737        let token = if self.policy.token_field.is_some() {
738            usize::from(self.limits.token())
739        } else {
740            0
741        };
742        self.token_area().saturating_add(token)
743    }
744
745    /// The location of the current part's header block.
746    fn head_at(&self) -> Location {
747        Location::in_part(self.parts.checked_sub(1), self.head_start)
748    }
749
750    /// The location of the first data byte past a budget of `limit` of
751    /// which `used` is spent, in the current part.
752    fn excess(&self, used: u64, limit: u64) -> Location {
753        let into_part = self.part_bytes.saturating_add(limit.saturating_sub(used));
754        Location::in_part(
755            self.parts.checked_sub(1),
756            self.data_start.saturating_add(into_part),
757        )
758    }
759
760    fn at(&self, pos: usize) -> Location {
761        let part = if matches!(self.state, State::Preamble | State::Epilogue) {
762            None
763        } else {
764            self.parts.checked_sub(1)
765        };
766        Location::in_part(part, self.bytes_seen.saturating_add(pos as u64))
767    }
768
769    /// Runs the state machine and returns positions only, so callers that
770    /// own the input for longer (the buffered parser) can slice it
771    /// themselves.
772    pub(crate) fn advance(
773        &mut self,
774        input: &[u8],
775        eof: bool,
776    ) -> Result<(usize, Outcome), FormError> {
777        if self.state == State::Poisoned {
778            return Err(FormError::Poisoned);
779        }
780        match self.run(input, eof) {
781            Ok((consumed, outcome)) => {
782                self.bytes_seen = self.bytes_seen.saturating_add(consumed as u64);
783                Ok((consumed, outcome))
784            }
785            Err(e) => {
786                self.state = State::Poisoned;
787                Err(e)
788            }
789        }
790    }
791
792    fn run(&mut self, input: &[u8], eof: bool) -> Result<(usize, Outcome), FormError> {
793        if self.bytes_seen.saturating_add(input.len() as u64) > self.limits.body() {
794            return Err(FormError::limit(
795                Limit::Body,
796                Location::at(self.limits.body()),
797            ));
798        }
799        let mut pos = 0usize;
800        loop {
801            let hay = input.get(pos..).unwrap_or(&[]);
802            match self.state {
803                State::Poisoned => return Err(FormError::Poisoned),
804                State::Preamble => {
805                    if let Some(after) = self.preamble_step(hay)? {
806                        pos = pos.wrapping_add(after);
807                        self.state = State::AfterDelimiter(After::Start);
808                        continue;
809                    }
810                    if eof {
811                        return Err(FormError::syntax(
812                            SyntaxError::NoInitialDelimiter,
813                            self.at(input.len()),
814                        ));
815                    }
816                    return Ok((input.len(), Outcome::NeedInput));
817                }
818                State::AfterDelimiter(sub) => {
819                    let Some(&c) = hay.first() else {
820                        return self.need_input(input.len(), eof);
821                    };
822                    pos = pos.wrapping_add(1);
823                    let at = self.at(pos);
824                    self.state = match (sub, c) {
825                        (After::Start, b'-') => State::AfterDelimiter(After::Dash),
826                        (After::Dash, b'-') => {
827                            self.state = State::Epilogue;
828                            self.epilogue_start = self.bytes_seen.saturating_add(pos as u64);
829                            return Ok((pos, Outcome::End));
830                        }
831                        (After::Start, b' ' | b'\t') => {
832                            if self.limits.max_padding_bytes == 0 {
833                                return Err(FormError::limit(Limit::Padding, at));
834                            }
835                            State::AfterDelimiter(After::Padding(1))
836                        }
837                        (After::Padding(n), b' ' | b'\t') => {
838                            if n >= self.limits.max_padding_bytes {
839                                return Err(FormError::limit(Limit::Padding, at));
840                            }
841                            State::AfterDelimiter(After::Padding(n.saturating_add(1)))
842                        }
843                        (After::Start | After::Padding(_), b'\r') => {
844                            State::AfterDelimiter(After::Cr)
845                        }
846                        (After::Cr, b'\n') => {
847                            self.parts = self.parts.saturating_add(1);
848                            if self.parts > self.limits.max_parts {
849                                return Err(FormError::limit(Limit::Parts, self.at(pos)));
850                            }
851                            self.scan = BlockScan::default();
852                            self.copied = 0;
853                            self.head_start = self.bytes_seen.saturating_add(pos as u64);
854                            State::Headers
855                        }
856                        (After::Start | After::Padding(_), b'\n') => {
857                            return Err(FormError::syntax(SyntaxError::BareLineFeed, at));
858                        }
859                        _ => return Err(FormError::syntax(SyntaxError::BoundaryInContent, at)),
860                    };
861                }
862                State::Headers => {
863                    let at = self.head_at();
864                    match self.scan.feed(hay, &self.limits, at)? {
865                        Some(end) => {
866                            let source = if self.copied == 0 {
867                                Source::Input(pos)
868                            } else {
869                                self.copy_header(hay.get(..end).unwrap_or(&[]), at)?;
870                                Source::Buffer
871                            };
872                            let block_start_in_input = pos;
873                            pos = pos.wrapping_add(end);
874                            let facts =
875                                self.read_head(input, source, block_start_in_input, end, at)?;
876                            let (is_token, is_charset) = {
877                                let block = self.block(&facts, source, input);
878                                let name = self.name_bytes(&facts, block);
879                                let token = self
880                                    .policy
881                                    .token_field
882                                    .is_some_and(|t| name == t.name().as_bytes());
883                                (token, name == b"_charset_")
884                            };
885                            self.start_part(&facts, is_token, is_charset, at)?;
886                            self.data_start = self.bytes_seen.saturating_add(pos as u64);
887                            return Ok((pos, Outcome::PartStart(facts, source)));
888                        }
889                        None => {
890                            self.copy_header(hay, at)?;
891                            return self.need_input(input.len(), eof);
892                        }
893                    }
894                }
895                State::Body => {
896                    if hay.is_empty() {
897                        return self.need_input(input.len(), eof);
898                    }
899                    match self.body_step(hay, pos, input.len(), eof)? {
900                        BodyStep::Emit(consumed, outcome) => return Ok((consumed, outcome)),
901                        BodyStep::Continue(next) => pos = next,
902                    }
903                }
904                State::EndOfPart => {
905                    let summary = self.end_part(self.at(pos))?;
906                    self.state = State::AfterDelimiter(After::Start);
907                    return Ok((pos, Outcome::PartEnd(summary)));
908                }
909                State::Epilogue => {
910                    self.epilogue = self.epilogue.saturating_add(hay.len() as u64);
911                    if self.epilogue > self.limits.epilogue() {
912                        let at = self.epilogue_start.saturating_add(self.limits.epilogue());
913                        return Err(FormError::limit(Limit::Epilogue, Location::at(at)));
914                    }
915                    return Ok((input.len(), Outcome::End));
916                }
917            }
918        }
919    }
920
921    fn need_input(&self, len: usize, eof: bool) -> Result<(usize, Outcome), FormError> {
922        if eof {
923            Err(FormError::syntax(SyntaxError::Truncated, self.at(len)))
924        } else {
925            Ok((len, Outcome::NeedInput))
926        }
927    }
928
929    /// Scans the preamble. Returns the index in `hay` just after the first
930    /// delimiter, or `None` when all of `hay` was consumed.
931    fn preamble_step(&mut self, hay: &[u8]) -> Result<Option<usize>, FormError> {
932        let len = self.boundary.len() as u64;
933        // A delimiter must end within max_preamble_bytes + its own length.
934        let budget = self.limits.preamble().saturating_add(len);
935        let room = usize::try_from(budget.saturating_sub(self.preamble)).unwrap_or(usize::MAX);
936        let window = hay.get(..room.min(hay.len())).unwrap_or(hay);
937        let (k, found) = self.boundary.run(usize::from(self.k), window);
938        self.k = k as u8;
939        match found {
940            Some(after) => {
941                // Real bytes up to the end of the delimiter; a delimiter that
942                // includes the two virtual bytes (CRLF) starts the body.
943                let end = self.preamble.saturating_add(after as u64);
944                let preamble = end.saturating_sub(len);
945                if preamble > self.limits.preamble() {
946                    return Err(FormError::limit(
947                        Limit::Preamble,
948                        Location::at(self.limits.preamble()),
949                    ));
950                }
951                self.k = 0;
952                Ok(Some(after))
953            }
954            None => {
955                self.preamble = self.preamble.saturating_add(window.len() as u64);
956                if window.len() < hay.len() || self.preamble >= budget {
957                    return Err(FormError::limit(
958                        Limit::Preamble,
959                        Location::at(self.limits.preamble()),
960                    ));
961                }
962                Ok(None)
963            }
964        }
965    }
966
967    fn copy_header(&mut self, bytes: &[u8], at: Location) -> Result<(), FormError> {
968        let start = self.copied as usize;
969        let end = start.saturating_add(bytes.len());
970        if end > self.limits.max_part_header_bytes as usize {
971            return Err(FormError::limit(Limit::PartHeaderBytes, at));
972        }
973        match self.buffer.as_mut().get_mut(start..end) {
974            Some(dst) => dst.copy_from_slice(bytes),
975            None => return Err(FormError::limit(Limit::PartHeaderBytes, at)),
976        }
977        self.copied = end as u32;
978        Ok(())
979    }
980
981    fn read_head(
982        &mut self,
983        input: &[u8],
984        source: Source,
985        start: usize,
986        end: usize,
987        at: Location,
988    ) -> Result<HeadFacts, FormError> {
989        let name_area = self.name_area();
990        let filename_area = self.filename_area();
991        let filename_end = self.token_area();
992        let limits = self.limits;
993        let policy = self.policy;
994        let copied = self.copied as usize;
995        let buf = self.buffer.as_mut();
996        let (head, rest) = buf.split_at_mut(name_area.min(buf.len()));
997        let (name, rest) =
998            rest.split_at_mut(filename_area.saturating_sub(name_area).min(rest.len()));
999        let filename = rest
1000            .get_mut(..filename_end.saturating_sub(filename_area))
1001            .unwrap_or(&mut []);
1002        let block = match source {
1003            Source::Input(_) => input.get(start..start.saturating_add(end)).unwrap_or(&[]),
1004            Source::Buffer => head.get(..copied).unwrap_or(&[]),
1005        };
1006        read_block(block, &limits, &policy, Areas { name, filename }, at)
1007    }
1008
1009    /// The budgets and rules that apply when a part starts.
1010    fn start_part(
1011        &mut self,
1012        facts: &HeadFacts,
1013        is_token: bool,
1014        is_charset: bool,
1015        at: Location,
1016    ) -> Result<(), FormError> {
1017        self.state = State::Body;
1018        self.k = 0;
1019        self.flags = 0;
1020        self.kind = facts.kind;
1021        self.part_bytes = 0;
1022        match facts.kind {
1023            PartKind::Field => {
1024                self.fields = self.fields.saturating_add(1);
1025                if self.fields > self.limits.max_fields {
1026                    return Err(FormError::limit(Limit::Fields, at));
1027                }
1028                if is_token {
1029                    if self.token != Token::NotSeen {
1030                        return Err(FormError::policy(PolicyRefusal::DuplicateToken, at));
1031                    }
1032                    self.token = Token::Capturing;
1033                    self.token_len = 0;
1034                    self.flags |= IS_TOKEN;
1035                }
1036                if is_charset {
1037                    self.flags |= IS_CHARSET;
1038                    self.charset_len = 0;
1039                    self.charset_done = false;
1040                }
1041            }
1042            PartKind::File => {
1043                let empty = matches!(facts.filename, Some((Text::Block(span), _)) if span.len == 0)
1044                    || matches!(facts.filename, Some((Text::Area(0), _)));
1045                if empty && self.policy.empty_files == EmptyFilePolicy::Skip {
1046                    self.flags |= DEFERRED;
1047                } else {
1048                    self.count_file(at)?;
1049                }
1050            }
1051        }
1052        Ok(())
1053    }
1054
1055    /// The block of a head, wherever it is.
1056    fn block<'a>(&'a self, facts: &HeadFacts, source: Source, input: &'a [u8]) -> &'a [u8] {
1057        let len = facts.block_len as usize;
1058        match source {
1059            Source::Input(start) => input.get(start..start.saturating_add(len)).unwrap_or(&[]),
1060            Source::Buffer => self.buffer.as_ref().get(..len).unwrap_or(&[]),
1061        }
1062    }
1063
1064    /// The decoded name of a head.
1065    fn name_bytes<'a>(&'a self, facts: &HeadFacts, block: &'a [u8]) -> &'a [u8] {
1066        match facts.name {
1067            Text::Block(span) => span.get(block),
1068            Text::Area(len) => {
1069                let start = self.name_area();
1070                self.buffer
1071                    .as_ref()
1072                    .get(start..start.saturating_add(len as usize))
1073                    .unwrap_or(&[])
1074            }
1075        }
1076    }
1077
1078    /// The borrowed view of a head.
1079    fn head_view<'p>(&'p self, facts: &HeadFacts, source: Source, input: &'p [u8]) -> PartHead<'p> {
1080        let block = self.block(facts, source, input);
1081        let name = core::str::from_utf8(self.name_bytes(facts, block)).unwrap_or("");
1082        let filename = facts.filename.map(|(text, origin)| {
1083            let raw = match text {
1084                Text::Block(span) => span.get(block),
1085                Text::Area(len) => {
1086                    let start = self.filename_area();
1087                    self.buffer
1088                        .as_ref()
1089                        .get(start..start.saturating_add(len as usize))
1090                        .unwrap_or(&[])
1091                }
1092            };
1093            FileName::new(raw, origin)
1094        });
1095        PartHead {
1096            index: self.parts.saturating_sub(1),
1097            block,
1098            name,
1099            name_raw: facts.name_raw.get(block),
1100            kind: facts.kind,
1101            filename,
1102            content_type: facts.content_type.map(|span| span.get(block)),
1103            charset: facts.charset,
1104        }
1105    }
1106
1107    /// Turns positions into borrowed events.
1108    fn view<'p>(&'p self, outcome: Outcome, input: &'p [u8]) -> Event<'p> {
1109        match outcome {
1110            Outcome::PartStart(facts, source) => {
1111                Event::PartStart(self.head_view(&facts, source, input))
1112            }
1113            Outcome::DataInput(start, end) => Event::Data(input.get(start..end).unwrap_or(&[])),
1114            Outcome::DataDelimiter(n) => {
1115                Event::Data(self.boundary.delimiter().get(..n).unwrap_or(&[]))
1116            }
1117            Outcome::PartEnd(summary) => Event::PartEnd(summary),
1118            Outcome::End => Event::End,
1119            Outcome::NeedInput => Event::NeedInput,
1120        }
1121    }
1122
1123    fn count_file(&mut self, at: Location) -> Result<(), FormError> {
1124        self.files = self.files.saturating_add(1);
1125        if self.files > self.limits.max_files {
1126            return Err(FormError::limit(Limit::Files, at));
1127        }
1128        if let Some(t) = self.policy.token_field {
1129            if t.placement() == TokenPlacement::BeforeFirstFile && self.token != Token::Captured {
1130                return Err(FormError::policy(PolicyRefusal::TokenFieldLate, at));
1131            }
1132        }
1133        Ok(())
1134    }
1135
1136    /// Checks budgets for `n` more data bytes of the current part, and
1137    /// captures the token or `_charset_` value.
1138    fn take_data(&mut self, bytes: &[u8]) -> Result<(), FormError> {
1139        let n = bytes.len() as u64;
1140        if n == 0 {
1141            return Ok(());
1142        }
1143        if self.flags & DEFERRED != 0 {
1144            self.flags &= !DEFERRED;
1145            let at = Location::in_part(self.parts.checked_sub(1), self.data_start);
1146            self.count_file(at)?;
1147        }
1148        let total = self.part_bytes.saturating_add(n);
1149        match self.kind {
1150            PartKind::File => {
1151                let limit = self.limits.file();
1152                if total > limit {
1153                    let at = self.excess(self.part_bytes, limit);
1154                    return Err(FormError::limit(Limit::FileSize, at));
1155                }
1156            }
1157            PartKind::Field => {
1158                let limit = u64::from(self.limits.value());
1159                if total > limit {
1160                    let at = self.excess(self.part_bytes, limit);
1161                    return Err(FormError::limit(Limit::ValueLength, at));
1162                }
1163                let text = self.text_total.saturating_add(n);
1164                let limit = self.limits.text();
1165                if text > limit {
1166                    let at = self.excess(self.text_total, limit);
1167                    return Err(FormError::limit(Limit::TextTotal, at));
1168                }
1169                self.text_total = text;
1170            }
1171        }
1172        if self.flags & IS_TOKEN != 0 {
1173            let start = self
1174                .token_area()
1175                .saturating_add(usize::from(self.token_len));
1176            let end = start.saturating_add(bytes.len());
1177            let limit = u64::from(self.limits.token());
1178            let at = self.excess(u64::from(self.token_len), limit);
1179            if end
1180                > self
1181                    .token_area()
1182                    .saturating_add(usize::from(self.limits.token()))
1183            {
1184                return Err(FormError::limit(Limit::TokenLength, at));
1185            }
1186            match self.buffer.as_mut().get_mut(start..end) {
1187                Some(dst) => dst.copy_from_slice(bytes),
1188                None => return Err(FormError::limit(Limit::TokenLength, at)),
1189            }
1190            self.token_len = self.token_len.saturating_add(bytes.len() as u16);
1191        }
1192        if self.flags & IS_CHARSET != 0 {
1193            let start = self
1194                .charset_area()
1195                .saturating_add(usize::from(self.charset_len));
1196            let end = start.saturating_add(bytes.len());
1197            let at = self.excess(u64::from(self.charset_len), CHARSET_CAPTURE as u64);
1198            if end > self.charset_area().saturating_add(CHARSET_CAPTURE) {
1199                return Err(FormError::encoding(EncodingError::UnsupportedCharset, at));
1200            }
1201            match self.buffer.as_mut().get_mut(start..end) {
1202                Some(dst) => dst.copy_from_slice(bytes),
1203                None => return Err(FormError::encoding(EncodingError::UnsupportedCharset, at)),
1204            }
1205            self.charset_len = self.charset_len.saturating_add(bytes.len() as u8);
1206        }
1207        self.part_bytes = total;
1208        Ok(())
1209    }
1210
1211    fn end_part(&mut self, at: Location) -> Result<PartSummary, FormError> {
1212        if self.flags & IS_TOKEN != 0 {
1213            let start = self.token_area();
1214            let bytes = self
1215                .buffer
1216                .as_ref()
1217                .get(start..start.saturating_add(usize::from(self.token_len)))
1218                .unwrap_or(&[]);
1219            if core::str::from_utf8(bytes).is_err() {
1220                return Err(FormError::encoding(EncodingError::InvalidUtf8, at));
1221            }
1222            self.token = Token::Captured;
1223        }
1224        if self.flags & IS_CHARSET != 0 {
1225            self.charset_done = true;
1226        }
1227        let summary = PartSummary {
1228            index: self.parts.saturating_sub(1),
1229            kind: self.kind,
1230            bytes: self.part_bytes,
1231            skipped: self.flags & SKIPPING != 0,
1232        };
1233        self.flags = 0;
1234        self.k = 0;
1235        Ok(summary)
1236    }
1237
1238    /// One step in a part's data. `pos` is where `hay` starts in the input.
1239    fn body_step(
1240        &mut self,
1241        hay: &[u8],
1242        pos: usize,
1243        input_len: usize,
1244        eof: bool,
1245    ) -> Result<BodyStep, FormError> {
1246        let len = self.boundary.len();
1247        let k0 = usize::from(self.k);
1248        if k0 > 0 {
1249            // Bytes from earlier chunks that matched the start of the
1250            // delimiter: resolve them before anything else.
1251            let mut k = k0;
1252            for (i, &c) in hay.iter().enumerate() {
1253                k = self.boundary.step(k, c);
1254                let n = i.wrapping_add(1);
1255                if k == len {
1256                    // The delimiter started inside the held bytes.
1257                    let data = k0.saturating_add(n).saturating_sub(len);
1258                    self.k = 0;
1259                    self.state = State::EndOfPart;
1260                    return self.emit_delimiter_bytes(data, pos.wrapping_add(n));
1261                }
1262                if k <= n {
1263                    // Every held byte is data; scan the input again from 0.
1264                    self.k = 0;
1265                    return self.emit_delimiter_bytes(k0, pos);
1266                }
1267            }
1268            // All of `hay` extended the partial match.
1269            let n = hay.len();
1270            if eof {
1271                return Err(FormError::syntax(
1272                    SyntaxError::Truncated,
1273                    self.at(input_len),
1274                ));
1275            }
1276            let data = k0.saturating_add(n).saturating_sub(k);
1277            self.k = k as u8;
1278            return self.emit_delimiter_bytes(data, input_len);
1279        }
1280        let (k, found) = self.boundary.run(0, hay);
1281        match found {
1282            Some(after) => {
1283                let data = after.saturating_sub(len);
1284                self.k = 0;
1285                self.state = State::EndOfPart;
1286                let bytes = hay.get(..data).unwrap_or(&[]);
1287                self.emit_input_bytes(bytes, pos, pos.wrapping_add(data), pos.wrapping_add(after))
1288            }
1289            None => {
1290                if eof {
1291                    return Err(FormError::syntax(
1292                        SyntaxError::Truncated,
1293                        self.at(input_len),
1294                    ));
1295                }
1296                self.k = k as u8;
1297                let data_end = hay.len().saturating_sub(k);
1298                let bytes = hay.get(..data_end).unwrap_or(&[]);
1299                self.emit_input_bytes(bytes, pos, pos.wrapping_add(data_end), input_len)
1300            }
1301        }
1302    }
1303
1304    /// Emits `delimiter[..n]` as data (or skips it), consuming up to
1305    /// `consumed`.
1306    fn emit_delimiter_bytes(&mut self, n: usize, consumed: usize) -> Result<BodyStep, FormError> {
1307        let boundary = self.boundary;
1308        let bytes = boundary.delimiter().get(..n).unwrap_or(&[]);
1309        self.take_data(bytes)?;
1310        if n == 0 || self.flags & SKIPPING != 0 {
1311            return Ok(BodyStep::Continue(consumed));
1312        }
1313        Ok(BodyStep::Emit(consumed, Outcome::DataDelimiter(n)))
1314    }
1315
1316    /// Emits `input[start..end]` as data (or skips it), consuming up to
1317    /// `consumed`.
1318    fn emit_input_bytes(
1319        &mut self,
1320        bytes: &[u8],
1321        start: usize,
1322        end: usize,
1323        consumed: usize,
1324    ) -> Result<BodyStep, FormError> {
1325        self.take_data(bytes)?;
1326        if start == end || self.flags & SKIPPING != 0 {
1327            return Ok(BodyStep::Continue(consumed));
1328        }
1329        Ok(BodyStep::Emit(consumed, Outcome::DataInput(start, end)))
1330    }
1331}
1332
1333/// What a body step did.
1334enum BodyStep {
1335    Emit(usize, Outcome),
1336    Continue(usize),
1337}
1338
1339impl<B: AsMut<[u8]> + AsRef<[u8]>> Drop for MultipartParser<B> {
1340    fn drop(&mut self) {
1341        crate::wipe::wipe(self.buffer.as_mut());
1342    }
1343}
1344
1345#[cfg(test)]
1346mod tests {
1347    use super::*;
1348
1349    #[test]
1350    fn test_parser_size_is_bounded() {
1351        // NFR-18 as measured: the boundary and its matcher table are 149 of
1352        // these bytes; the buffer is separate.
1353        assert!(core::mem::size_of::<MultipartParser<&mut [u8]>>() <= 384);
1354        assert!(core::mem::size_of::<Boundary>() <= 150);
1355    }
1356
1357    #[cfg(feature = "alloc")]
1358    #[test]
1359    fn test_debug_never_shows_data_or_file_names() {
1360        let body = b"--X\r\nContent-Disposition: form-data; name=f; filename=\"secret.pdf\"\r\n\r\nhunter2\r\n--X--";
1361        let mut p = MultipartParser::new(
1362            Boundary::new(b"X").unwrap(),
1363            &Limits::DEFAULT,
1364            &Policy::DEFAULT,
1365        )
1366        .unwrap();
1367        let mut rest: &[u8] = body;
1368        let mut seen = alloc::string::String::new();
1369        loop {
1370            let step = p.parse(rest, true).unwrap();
1371            rest = &rest[step.consumed..];
1372            if let Event::PartStart(head) = &step.event {
1373                seen.push_str(&alloc::format!("{:?}", head.headers()));
1374            }
1375            seen.push_str(&alloc::format!("{step:?}"));
1376            if matches!(step.event, Event::End) {
1377                break;
1378            }
1379        }
1380        assert!(seen.contains("Data(7 bytes)"), "{seen}");
1381        assert!(
1382            !seen.contains("hunter2") && !seen.contains("secret"),
1383            "{seen}"
1384        );
1385    }
1386
1387    #[test]
1388    fn test_charset_labels() {
1389        assert_eq!(Charset::from_label(b"UTF-8"), Charset::Utf8);
1390        assert_eq!(Charset::from_label(b"us-ascii"), Charset::Ascii);
1391        assert_eq!(Charset::from_label(b"utf8"), Charset::Other);
1392    }
1393}