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