Skip to main content

s3s_multipart/
multipart.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023-2026 The s3s Authors
3
4use crate::buffer::StreamBuffer;
5use crate::delimiter::{DataSearch, make_delimiter_finder, make_first_boundary, search_data};
6use crate::header::HeaderBlock;
7use crate::part::Part;
8use crate::part_data_stream::PartDataStream;
9use crate::{Boundary, Error};
10
11use std::future::poll_fn;
12use std::task::{Context, Poll, ready};
13
14use bytes::Bytes;
15use futures_core::Stream;
16use memchr::memmem;
17
18/// A streaming parser for `multipart/form-data`.
19///
20/// The parser owns the underlying stream and yields one [`Part`] at a time.
21/// At most one `Part` is active at any moment; the borrow checker enforces
22/// this property at compile time.
23///
24/// The stream has to be `Send + Unpin`, but not `Sync`: the parser only ever
25/// polls it through `&mut`, so it never needs shared access, and requiring
26/// `Sync` would turn away streams that can be moved between threads but not
27/// shared between them.
28pub struct Multipart<S>
29where
30    S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
31{
32    /// `None` only after [`Multipart::take_data_stream`] handed the buffer to a
33    /// [`PartDataStream`], which also moves the state to `StreamTaken`. Both
34    /// public entry points check that state first, so the
35    /// `Err(Error::StreamAlreadyTaken)` arms in the poll steps below are guards
36    /// rather than reachable outcomes.
37    buffer: Option<StreamBuffer<S>>,
38    first_boundary: Box<[u8]>,
39    /// Taken (not cloned) by [`Multipart::take_data_stream`]: `Some` while the
40    /// parser may still read part data, `None` once the stream is handed over.
41    /// Boxing keeps the parser compact — the finder carries a 288-byte searcher.
42    delimiter_finder: Option<Box<memmem::Finder<'static>>>,
43    pub(super) max_buffer_size: usize,
44    pub(super) state: State,
45    pub(super) headers: Option<HeaderBlock>,
46    /// A [`Part`] has been handed out and has not reached its data phase yet.
47    ///
48    /// The part borrows the parser, so dropping it is the only way for a caller
49    /// to say "not this one". Without this flag the next [`Multipart::next_part`]
50    /// would hand out the same part again, and a caller that keeps skipping
51    /// parts without reading their headers would loop without making progress.
52    /// While it is set, `next_part` discards the unread header block and skips
53    /// the part's data instead.
54    pub(super) part_handed_out: bool,
55}
56
57impl<S> Multipart<S>
58where
59    S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
60{
61    /// Constructs a parser.
62    ///
63    /// `max_buffer_size` bounds the part header block: a block that ends within
64    /// the limit is accepted, and one that grows past it without a terminator is
65    /// rejected with [`Error::HeaderSizeExceeded`]. The limit is a property of
66    /// the body, not of the delivery — the same bytes parse the same way however
67    /// the transport splits them — so a chunk larger than `max_buffer_size`
68    /// carrying a block that ends within the limit is accepted.
69    ///
70    /// Data chunks yielded by `stream` while part data is being streamed are
71    /// passed through without being retained, so they are not charged to this
72    /// limit. The buffer can still hold one arriving chunk beyond the limit: the
73    /// bytes that follow the header terminator are retained until the header
74    /// block is consumed, so the limit bounds the block, not the peak allocation.
75    pub fn new(stream: S, boundary: &Boundary, max_buffer_size: usize) -> Self {
76        let first_boundary = make_first_boundary(boundary.as_bytes());
77        let delimiter_finder = make_delimiter_finder(boundary.as_bytes());
78        Self {
79            buffer: Some(StreamBuffer::new(stream)),
80            first_boundary,
81            delimiter_finder: Some(delimiter_finder),
82            max_buffer_size,
83            state: State::FindingFirstBoundary,
84            headers: None,
85            part_handed_out: false,
86        }
87    }
88
89    /// Returns the next part, or `None` after the closing delimiter.
90    ///
91    /// Dropping a [`Part`] without entering its data phase (through
92    /// [`Part::next_data`] or [`Part::take_data_stream`]) skips the rest of
93    /// that part: unread headers are discarded and the part's data is skipped
94    /// lazily, so a caller can leave a part behind and keep going.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error for malformed input, stream errors, or when the
99    /// multipart stream has already been taken by a part data stream.
100    pub async fn next_part(&mut self) -> Result<Option<Part<'_, S>>, Error> {
101        loop {
102            match self.state {
103                State::StreamTaken => return Err(Error::StreamAlreadyTaken),
104                State::Done => return Ok(None),
105                State::FindingFirstBoundary => {
106                    poll_fn(|cx| self.poll_advance_first_boundary(cx)).await?;
107                }
108                State::ReadingPartHeaders => {
109                    if self.part_handed_out {
110                        // The previous part was dropped before its data phase:
111                        // finish the header block and let the loop skip its data.
112                        poll_fn(|cx| self.poll_ensure_headers(cx)).await?;
113                        self.finish_headers();
114                        continue;
115                    }
116                    self.part_handed_out = true;
117                    return Ok(Some(Part::new(self)));
118                }
119                State::ReadingPartData => {
120                    poll_fn(|cx| self.poll_skip_part_data(cx)).await?;
121                    poll_fn(|cx| self.poll_advance_after_boundary(cx)).await?;
122                }
123                State::ReadingBoundary => {
124                    poll_fn(|cx| self.poll_advance_after_boundary(cx)).await?;
125                }
126                State::ReadingClosingDelimiter => {
127                    poll_fn(|cx| self.poll_advance_closing(cx)).await?;
128                }
129                State::ReadingEpilogue => {
130                    poll_fn(|cx| self.poll_epilogue(cx)).await?;
131                }
132            }
133        }
134    }
135
136    /// Poll-based core of the state machine, driven by [`Multipart::next_part`].
137    pub fn poll_next_part(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<Part<'_, S>>, Error>> {
138        loop {
139            match self.state {
140                State::StreamTaken => return Poll::Ready(Err(Error::StreamAlreadyTaken)),
141                State::Done => return Poll::Ready(Ok(None)),
142                State::FindingFirstBoundary => {
143                    ready!(self.poll_advance_first_boundary(cx)?);
144                }
145                State::ReadingPartHeaders => {
146                    if self.part_handed_out {
147                        // See `Multipart::next_part`: a dropped part is skipped.
148                        ready!(self.poll_ensure_headers(cx)?);
149                        self.finish_headers();
150                        continue;
151                    }
152                    self.part_handed_out = true;
153                    return Poll::Ready(Ok(Some(Part::new(self))));
154                }
155                State::ReadingPartData => {
156                    ready!(self.poll_skip_part_data(cx)?);
157                    ready!(self.poll_advance_after_boundary(cx)?);
158                }
159                State::ReadingBoundary => {
160                    ready!(self.poll_advance_after_boundary(cx)?);
161                }
162                State::ReadingClosingDelimiter => {
163                    ready!(self.poll_advance_closing(cx)?);
164                }
165                State::ReadingEpilogue => {
166                    ready!(self.poll_epilogue(cx)?);
167                }
168            }
169        }
170    }
171
172    /// Consumes the leading `--boundary`. Only this step runs here: once the
173    /// needle is gone the caller loops and re-matches the state, so a `Pending`
174    /// after the state moved on cannot re-run the search (which would look for
175    /// the *next* boundary and silently skip the part in between).
176    fn poll_advance_first_boundary(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
177        // Borrow the fields instead of cloning `first_boundary` into a local:
178        // the needle is a `Box<[u8]>`, so a clone would allocate on every poll
179        // of this step. Destructuring splits the borrows, which `buffer_mut()`
180        // (a `&mut self` borrow) cannot do for us.
181        let Self {
182            buffer, first_boundary, ..
183        } = self;
184        let Some(buffer) = buffer.as_mut() else {
185            return Poll::Ready(Err(Error::StreamAlreadyTaken));
186        };
187        ready!(buffer.poll_read_to(first_boundary, cx)?);
188        self.state = State::ReadingBoundary;
189        Poll::Ready(Ok(()))
190    }
191
192    fn poll_advance_after_boundary(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
193        ready!(self.poll_ensure_buf_len(1, cx)?);
194        let first = match self.buffer_mut() {
195            Ok(buffer) => buffer.buf[0],
196            Err(err) => return Poll::Ready(Err(err)),
197        };
198        match first {
199            b'-' => {
200                ready!(self.poll_ensure_buf_len(2, cx)?);
201                let bytes = match self.buffer_mut() {
202                    Ok(buffer) => &buffer.buf,
203                    Err(err) => return Poll::Ready(Err(err)),
204                };
205                if bytes.get(..2) != Some(&b"--"[..]) {
206                    return Poll::Ready(Err(Error::InvalidFormat));
207                }
208                if let Ok(buffer) = self.buffer_mut() {
209                    let _ = buffer.buf.split_to(2);
210                }
211                self.state = State::ReadingClosingDelimiter;
212                Poll::Ready(Ok(()))
213            }
214            b' ' | b'\t' => {
215                ready!(self.poll_consume_padding_and_crlf(cx)?);
216                self.state = State::ReadingPartHeaders;
217                Poll::Ready(Ok(()))
218            }
219            b'\r' => {
220                ready!(self.poll_consume_crlf(cx)?);
221                self.state = State::ReadingPartHeaders;
222                Poll::Ready(Ok(()))
223            }
224            _ => Poll::Ready(Err(Error::InvalidFormat)),
225        }
226    }
227
228    /// Consumes the closing delimiter's padding and CRLF, then hands the strict
229    /// trailer check to [`Multipart::poll_epilogue`]. Keeping the epilogue in
230    /// its own state is what makes the resume safe: re-running this step after a
231    /// `Pending` would consume a CRLF that is already gone and then wait for
232    /// bytes that never come.
233    fn poll_advance_closing(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
234        ready!(self.poll_consume_padding_and_crlf(cx)?);
235        self.state = State::ReadingEpilogue;
236        Poll::Ready(Ok(()))
237    }
238
239    /// Strict trailer: after the closing delimiter only end of stream may
240    /// follow, with the closing CRLF already consumed.
241    fn poll_epilogue(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
242        let buffer = match self.buffer_mut() {
243            Ok(buffer) => buffer,
244            Err(err) => return Poll::Ready(Err(err)),
245        };
246        if !buffer.buf.is_empty() {
247            return Poll::Ready(Err(Error::StreamPartNotLast));
248        }
249        match ready!(buffer.poll_stream(cx)) {
250            None => {
251                self.state = State::Done;
252                Poll::Ready(Ok(()))
253            }
254            // A chunk that carries no bytes is not trailing content, and
255            // `poll_stream` never hands one out, so whatever arrives here is
256            // content after the closing delimiter.
257            Some(Ok(_)) => Poll::Ready(Err(Error::StreamPartNotLast)),
258            Some(Err(err)) => Poll::Ready(Err(err)),
259        }
260    }
261
262    fn poll_consume_padding_and_crlf(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
263        loop {
264            ready!(self.poll_ensure_buf_len(1, cx)?);
265            let first = match self.buffer_mut() {
266                Ok(buffer) => buffer.buf[0],
267                Err(err) => return Poll::Ready(Err(err)),
268            };
269            if first == b'\r' {
270                return self.poll_consume_crlf(cx);
271            }
272            if !matches!(first, b' ' | b'\t') {
273                return Poll::Ready(Err(Error::InvalidFormat));
274            }
275
276            let padding_len = match self.buffer_mut() {
277                Ok(buffer) => buffer.buf.iter().take_while(|&&b| matches!(b, b' ' | b'\t')).count(),
278                Err(err) => return Poll::Ready(Err(err)),
279            };
280            if let Ok(buffer) = self.buffer_mut() {
281                let _ = buffer.buf.split_to(padding_len);
282            }
283        }
284    }
285
286    fn poll_consume_crlf(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
287        ready!(self.poll_ensure_buf_len(2, cx)?);
288        let bytes = match self.buffer_mut() {
289            Ok(buffer) => &buffer.buf,
290            Err(err) => return Poll::Ready(Err(err)),
291        };
292        if bytes.get(..2) != Some(&b"\r\n"[..]) {
293            return Poll::Ready(Err(Error::InvalidFormat));
294        }
295        if let Ok(buffer) = self.buffer_mut() {
296            let _ = buffer.buf.split_to(2);
297        }
298        Poll::Ready(Ok(()))
299    }
300
301    fn poll_ensure_buf_len(&mut self, len: usize, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
302        while match self.buffer_mut() {
303            Ok(buffer) => buffer.buf.len() < len,
304            Err(err) => return Poll::Ready(Err(err)),
305        } {
306            let buffer = match self.buffer_mut() {
307                Ok(buffer) => buffer,
308                Err(err) => return Poll::Ready(Err(err)),
309            };
310            match ready!(buffer.poll_stream(cx)) {
311                Some(Ok(chunk)) => {
312                    if let Ok(buffer) = self.buffer_mut() {
313                        buffer.buf.extend_from_slice(&chunk);
314                    }
315                }
316                Some(Err(err)) => return Poll::Ready(Err(err)),
317                None => return Poll::Ready(Err(Error::IncompleteStream)),
318            }
319        }
320        Poll::Ready(Ok(()))
321    }
322
323    pub(super) fn poll_next_part_data_chunk(&mut self, cx: &mut Context<'_>) -> Poll<Result<Option<Bytes>, Error>> {
324        if self.state != State::ReadingPartData {
325            return Poll::Ready(Ok(None));
326        }
327
328        // While `ReadingPartData` the finder is always present: only
329        // `take_data_stream` clears it, and that also moves the state away.
330        let Some(delimiter_finder) = self.delimiter_finder.as_deref() else {
331            return Poll::Ready(Ok(None));
332        };
333        let delimiter_len = delimiter_finder.needle().len();
334
335        loop {
336            {
337                let Some(buffer) = self.buffer.as_mut() else {
338                    return Poll::Ready(Err(Error::StreamAlreadyTaken));
339                };
340                if !buffer.buf.is_empty() {
341                    match search_data(&buffer.buf, delimiter_finder) {
342                        DataSearch::Found { index } => {
343                            let data = buffer.buf.split_to(index).freeze();
344                            let _ = buffer.buf.split_to(delimiter_len);
345                            self.state = State::ReadingBoundary;
346                            return if data.is_empty() {
347                                Poll::Ready(Ok(None))
348                            } else {
349                                Poll::Ready(Ok(Some(data)))
350                            };
351                        }
352                        DataSearch::Emit { end } => {
353                            let data = buffer.buf.split_to(end).freeze();
354                            return Poll::Ready(Ok(Some(data)));
355                        }
356                        DataSearch::KeepAll => {}
357                    }
358                }
359            }
360
361            let Some(buffer) = self.buffer.as_mut() else {
362                return Poll::Ready(Err(Error::StreamAlreadyTaken));
363            };
364            match ready!(buffer.poll_stream(cx)) {
365                Some(Ok(chunk)) => {
366                    let Some(buffer) = self.buffer.as_mut() else {
367                        return Poll::Ready(Err(Error::StreamAlreadyTaken));
368                    };
369                    if !buffer.buf.is_empty() {
370                        buffer.buf.extend_from_slice(&chunk);
371                        continue;
372                    }
373
374                    match search_data(&chunk, delimiter_finder) {
375                        DataSearch::Found { index } => {
376                            let data = chunk.slice(..index);
377                            buffer.buf.clear();
378                            buffer.buf.extend_from_slice(&chunk[index.saturating_add(delimiter_len)..]);
379                            self.state = State::ReadingBoundary;
380                            return if data.is_empty() {
381                                Poll::Ready(Ok(None))
382                            } else {
383                                Poll::Ready(Ok(Some(data)))
384                            };
385                        }
386                        DataSearch::Emit { end } => {
387                            let data = chunk.slice(..end);
388                            buffer.buf.extend_from_slice(&chunk[end..]);
389                            return Poll::Ready(Ok(Some(data)));
390                        }
391                        DataSearch::KeepAll => {
392                            buffer.buf.extend_from_slice(&chunk);
393                        }
394                    }
395                }
396                Some(Err(err)) => return Poll::Ready(Err(err)),
397                None => return Poll::Ready(Err(Error::IncompleteStream)),
398            }
399        }
400    }
401
402    fn poll_skip_part_data(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
403        loop {
404            match ready!(self.poll_next_part_data_chunk(cx)) {
405                Ok(Some(_)) => {}
406                Ok(None) => return Poll::Ready(Ok(())),
407                Err(err) => return Poll::Ready(Err(err)),
408            }
409        }
410    }
411
412    pub(super) fn take_data_stream(&mut self) -> Result<PartDataStream<S>, Error> {
413        if self.state == State::ReadingPartHeaders {
414            self.finish_headers();
415        }
416        if self.state != State::ReadingPartData {
417            return Err(Error::InvalidFormat);
418        }
419
420        let Some(buffer) = self.buffer.take() else {
421            return Err(Error::StreamAlreadyTaken);
422        };
423        // Taken, not cloned: after the handover the parser cannot read part data
424        // any more, so `PartDataStream` may as well own the finder. Reaching the
425        // `else` arm would need a buffer without a finder, which nothing creates
426        // (both are cleared together, after the state guard above).
427        let Some(delimiter_finder) = self.delimiter_finder.take() else {
428            return Err(Error::StreamAlreadyTaken);
429        };
430        let multipart_consumed = buffer.consumed();
431        self.state = State::StreamTaken;
432
433        Ok(PartDataStream::new(buffer, delimiter_finder, multipart_consumed))
434    }
435
436    pub(super) fn buffer_mut(&mut self) -> Result<&mut StreamBuffer<S>, Error> {
437        self.buffer.as_mut().ok_or(Error::StreamAlreadyTaken)
438    }
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
442/// State of the parser state machine.
443///
444/// The module is private and `State` is not re-exported, so `pub` cannot make
445/// it reachable from outside the crate.
446pub enum State {
447    FindingFirstBoundary,
448    ReadingPartHeaders,
449    ReadingPartData,
450    ReadingBoundary,
451    ReadingClosingDelimiter,
452    /// Closing delimiter consumed; only end of stream may follow.
453    ReadingEpilogue,
454    Done,
455    StreamTaken,
456}
457
458#[cfg(test)]
459#[allow(clippy::expect_used, clippy::panic, clippy::unreachable, clippy::unwrap_used)]
460mod tests {
461    use super::*;
462
463    use futures::executor::block_on;
464    use futures_util::StreamExt;
465    use futures_util::stream;
466
467    fn parser(payload: &'static [u8]) -> Multipart<impl Stream<Item = Result<Bytes, Error>> + Send + Sync> {
468        Multipart::new(
469            stream::iter([Ok::<Bytes, std::io::Error>(Bytes::from_static(payload))])
470                .map(|item| item.map_err(Error::stream_read_failed)),
471            &Boundary::new(b"boundary").unwrap(),
472            1024,
473        )
474    }
475
476    fn chunked_parser(chunks: Vec<&'static [u8]>) -> Multipart<impl Stream<Item = Result<Bytes, Error>> + Send + Sync> {
477        Multipart::new(
478            stream::iter(chunks.into_iter().map(|c| Ok::<Bytes, std::io::Error>(Bytes::from_static(c))))
479                .map(|item| item.map_err(Error::stream_read_failed)),
480            &Boundary::new(b"boundary").unwrap(),
481            1024,
482        )
483    }
484
485    fn owned_chunk_parser_with_max(
486        chunks: Vec<Vec<u8>>,
487        max: usize,
488    ) -> Multipart<impl Stream<Item = Result<Bytes, Error>> + Send + Sync> {
489        Multipart::new(
490            stream::iter(chunks.into_iter().map(|c| Ok::<Bytes, std::io::Error>(Bytes::from(c))))
491                .map(|item| item.map_err(Error::stream_read_failed)),
492            &Boundary::new(b"boundary").unwrap(),
493            max,
494        )
495    }
496
497    fn owned_chunk_parser(chunks: Vec<Vec<u8>>) -> Multipart<impl Stream<Item = Result<Bytes, Error>> + Send + Sync> {
498        owned_chunk_parser_with_max(chunks, 1024)
499    }
500
501    async fn drain_part<S>(part: &mut Part<'_, S>) -> (Vec<(Vec<u8>, Vec<u8>)>, Vec<u8>)
502    where
503        S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
504    {
505        let mut headers = Vec::new();
506        while let Some(header) = part.next_header().await.unwrap() {
507            headers.push((header.name.as_bytes().to_vec(), header.value.to_vec()));
508        }
509        let mut data = Vec::new();
510        while let Some(chunk) = part.next_data().await.unwrap() {
511            data.extend_from_slice(&chunk);
512        }
513        (headers, data)
514    }
515
516    #[test]
517    fn parses_preamble_headers_data_and_close() {
518        block_on(async {
519            let mut mp = parser(b"preamble\r\n--boundary\r\nX-Test: one\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\nhello\r\n--boundary--\r\n");
520            let mut part = mp.next_part().await.unwrap().unwrap();
521            let (headers, data) = drain_part(&mut part).await;
522            assert_eq!(headers[0].0, b"X-Test");
523            assert_eq!(headers[0].1, b"one");
524            assert_eq!(headers[1].0, b"Content-Disposition");
525            assert_eq!(data, b"hello");
526            assert!(mp.next_part().await.unwrap().is_none());
527        });
528    }
529
530    /// `max_buffer_size` bounds the header block, not the delivery: a body whose
531    /// block fits within the limit parses the same way at every split, including
532    /// the splits that hand the parser a chunk larger than the limit.
533    #[test]
534    fn a_small_header_block_parses_at_every_split_position() {
535        block_on(async {
536            let mut body = b"--boundary\r\nA: b\r\n\r\n".to_vec();
537            body.extend(std::iter::repeat_n(b'x', 100));
538            body.extend_from_slice(b"\r\n--boundary--\r\n");
539
540            // The block ends six bytes into the header area, well inside the
541            // limit, while the largest chunk here is the whole 136-byte body.
542            for split in 0..=body.len() {
543                let mut mp = owned_chunk_parser_with_max(vec![body[..split].to_vec(), body[split..].to_vec()], 32);
544                let mut part = mp
545                    .next_part()
546                    .await
547                    .unwrap_or_else(|err| panic!("split {split}: {err}"))
548                    .unwrap();
549                let mut headers = 0usize;
550                while part.next_header().await.unwrap().is_some() {
551                    headers += 1;
552                }
553                let mut data = 0usize;
554                while let Some(chunk) = part.next_data().await.unwrap() {
555                    data += chunk.len();
556                }
557                assert_eq!((headers, data), (1, 100), "split {split}");
558                assert!(mp.next_part().await.unwrap().is_none(), "split {split}");
559            }
560        });
561    }
562
563    /// Dropping a `Part` without entering its data phase skips the rest of that
564    /// part. Before the parser tracked that, the next `next_part` handed out the
565    /// same part again, so a caller that skips parts without reading their
566    /// headers looped without ever making progress.
567    #[test]
568    fn dropping_a_part_skips_it() {
569        block_on(async {
570            let body: &[u8] = b"--boundary\r\nX: a\r\n\r\nfirst\r\n--boundary\r\nX: b\r\n\r\nsecond\r\n--boundary--\r\n";
571
572            // No header read at all: the part is skipped, and the loop ends.
573            let mut mp = owned_chunk_parser(vec![body.to_vec()]);
574            let mut visits = 0usize;
575            loop {
576                {
577                    // Dropped at the end of this block, without reading anything.
578                    let Some(part) = mp.next_part().await.unwrap() else { break };
579                    let _ = part;
580                }
581                visits += 1;
582                assert!(visits <= 2, "next_part re-delivered a dropped part");
583            }
584            assert_eq!(visits, 2);
585
586            // One header read before dropping: each part is visited exactly once.
587            let mut mp = owned_chunk_parser(vec![body.to_vec()]);
588            let mut seen = Vec::new();
589            while let Some(mut part) = mp.next_part().await.unwrap() {
590                let header = part.next_header().await.unwrap().unwrap();
591                seen.push(String::from_utf8(header.value.to_vec()).unwrap());
592                assert!(seen.len() <= 2, "next_part re-delivered a partly read part");
593            }
594            assert_eq!(seen, ["a", "b"]);
595        });
596    }
597
598    /// A dropped part whose header block is malformed reports the parse error
599    /// rather than re-delivering the same part: the block cannot be skipped
600    /// without parsing it, so the error is the only honest outcome.
601    #[test]
602    fn dropping_a_part_with_a_malformed_header_block_reports_the_error() {
603        block_on(async {
604            let mut mp = owned_chunk_parser(vec![b"--boundary\r\n\x01\x02bad\r\n\r\nDATA\r\n--boundary--\r\n".to_vec()]);
605            {
606                // Dropped without reading its header block.
607                let part = mp.next_part().await.unwrap().unwrap();
608                let _ = part;
609            }
610            assert!(matches!(mp.next_part().await, Err(Error::InvalidFormat)));
611        });
612    }
613
614    /// An empty header block is terminated by the blank line that follows the
615    /// boundary line, so the parse must not depend on where the transport split
616    /// the body: the block ends two bytes into the header area no matter which
617    /// chunk those bytes arrive in.
618    #[test]
619    fn empty_header_block_parses_at_every_split_position() {
620        block_on(async {
621            for (body, expected) in [
622                (&b"--boundary\r\n\r\nDATA\r\n--boundary--\r\n"[..], vec![(0usize, &b"DATA"[..])]),
623                (
624                    // Same shape on the second part, reached through the boundary skip.
625                    &b"--boundary\r\nX: y\r\n\r\nA\r\n--boundary\r\n\r\nDATA\r\n--boundary--\r\n"[..],
626                    vec![(1usize, &b"A"[..]), (0usize, &b"DATA"[..])],
627                ),
628            ] {
629                let want: Vec<(usize, Vec<u8>)> = expected.iter().map(|(n, data)| (*n, data.to_vec())).collect();
630                // Every split, including the degenerate ones that hand the parser
631                // an empty chunk: this is the shape a streaming transport
632                // produces, and the shape that used to fail whenever the boundary
633                // line ended the first chunk.
634                for split in 0..=body.len() {
635                    let mut mp = owned_chunk_parser(vec![body[..split].to_vec(), body[split..].to_vec()]);
636                    let mut seen: Vec<(usize, Vec<u8>)> = Vec::new();
637                    while let Some(mut part) = mp
638                        .next_part()
639                        .await
640                        .unwrap_or_else(|err| panic!("next_part failed at split {split}: {err}"))
641                    {
642                        let (headers, data) = drain_part(&mut part).await;
643                        seen.push((headers.len(), data));
644                    }
645                    assert_eq!(seen, want, "parts at split {split}");
646                }
647            }
648        });
649    }
650
651    /// A stream may hand out chunks that carry no bytes. They are not content,
652    /// so they must not be mistaken for an epilogue after the closing delimiter.
653    #[test]
654    fn empty_chunks_after_the_closing_delimiter_are_not_an_epilogue() {
655        block_on(async {
656            let mut mp = owned_chunk_parser(vec![b"--boundary\r\n\r\nDATA\r\n--boundary--\r\n".to_vec(), Vec::new(), Vec::new()]);
657            let mut part = mp.next_part().await.unwrap().unwrap();
658            assert!(part.next_header().await.unwrap().is_none());
659            let mut data = Vec::new();
660            while let Some(chunk) = part.next_data().await.unwrap() {
661                data.extend_from_slice(&chunk);
662            }
663            assert_eq!(data, b"DATA");
664            assert!(mp.next_part().await.unwrap().is_none());
665        });
666    }
667
668    /// A stream that never stops answering with empty chunks is not progress:
669    /// the parser must report a stream failure instead of polling it forever,
670    /// whether the run happens while reading the body or while checking for an
671    /// epilogue.
672    #[test]
673    fn an_endless_run_of_empty_chunks_is_a_stream_failure() {
674        struct BodyThenEmptyForever {
675            body: Option<Bytes>,
676        }
677
678        impl Stream for BodyThenEmptyForever {
679            type Item = Result<Bytes, Error>;
680
681            fn poll_next(mut self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
682                match self.body.take() {
683                    Some(body) => Poll::Ready(Some(Ok(body))),
684                    None => Poll::Ready(Some(Ok(Bytes::new()))),
685                }
686            }
687        }
688
689        block_on(async {
690            let mut mp = Multipart::new(
691                BodyThenEmptyForever {
692                    body: Some(Bytes::from_static(b"--boundary\r\n\r\nDATA\r\n--boundary--\r\n")),
693                },
694                &Boundary::new(b"boundary").unwrap(),
695                4096,
696            );
697            let mut part = mp.next_part().await.unwrap().unwrap();
698            assert!(part.next_header().await.unwrap().is_none());
699            let mut data = Vec::new();
700            while let Some(chunk) = part.next_data().await.unwrap() {
701                data.extend_from_slice(&chunk);
702            }
703            assert_eq!(data, b"DATA");
704            assert!(matches!(mp.next_part().await, Err(Error::StreamReadFailed(_))));
705        });
706
707        block_on(async {
708            // The same run while the header block is still incomplete.
709            let mut mp = Multipart::new(
710                BodyThenEmptyForever {
711                    body: Some(Bytes::from_static(b"--boundary\r\nA: b\r\n")),
712                },
713                &Boundary::new(b"boundary").unwrap(),
714                4096,
715            );
716            let mut part = mp.next_part().await.unwrap().unwrap();
717            assert!(matches!(part.next_header().await, Err(Error::StreamReadFailed(_))));
718        });
719    }
720
721    #[test]
722    fn skips_unread_part_data_when_dropped() {
723        block_on(async {
724            let mut mp = parser(b"--boundary\r\n\r\nskipped data\r\n--boundary\r\nX: y\r\n\r\nnext\r\n--boundary--\r\n");
725            {
726                let mut part = mp.next_part().await.unwrap().unwrap();
727                let header = part.next_header().await.unwrap();
728                assert!(header.is_none());
729                // drop without reading data
730            }
731            let mut part = mp.next_part().await.unwrap().unwrap();
732            let (headers, data) = drain_part(&mut part).await;
733            assert_eq!(headers[0].1, b"y");
734            assert_eq!(data, b"next");
735        });
736    }
737
738    #[test]
739    fn handles_boundary_split_across_chunks() {
740        block_on(async {
741            let mut mp = chunked_parser(vec![
742                b"--boundary\r\n\r\nabc\r\n--boun",
743                b"dary\r\nX: y\r\n\r\nz\r\n--boundary--\r\n",
744            ]);
745            let mut part = mp.next_part().await.unwrap().unwrap();
746            let (_, data) = drain_part(&mut part).await;
747            assert_eq!(data, b"abc");
748            let mut part = mp.next_part().await.unwrap().unwrap();
749            let (_, data) = drain_part(&mut part).await;
750            assert_eq!(data, b"z");
751        });
752    }
753
754    #[test]
755    fn accepts_transport_padding_after_boundary() {
756        block_on(async {
757            let mut mp = parser(b"--boundary \t\r\nX: y\r\n\r\ndata\r\n--boundary  \r\n\r\nnext\r\n--boundary--\t\r\n");
758            let mut part = mp.next_part().await.unwrap().unwrap();
759            let (_, data) = drain_part(&mut part).await;
760            assert_eq!(data, b"data");
761            let mut part = mp.next_part().await.unwrap().unwrap();
762            let (_, data) = drain_part(&mut part).await;
763            assert_eq!(data, b"next");
764            assert!(mp.next_part().await.unwrap().is_none());
765        });
766    }
767
768    #[test]
769    fn accepts_empty_header_and_empty_body_parts() {
770        block_on(async {
771            let mut mp = parser(b"--boundary\r\n\r\n\r\n--boundary\r\nX: y\r\n\r\n\r\n--boundary--\r\n");
772            let mut part = mp.next_part().await.unwrap().unwrap();
773            let (headers, data) = drain_part(&mut part).await;
774            assert_eq!(headers.len(), 0);
775            assert_eq!(data, b"");
776            let mut part = mp.next_part().await.unwrap().unwrap();
777            let (headers, data) = drain_part(&mut part).await;
778            assert_eq!(headers[0].1, b"y");
779            assert_eq!(data, b"");
780        });
781    }
782
783    #[test]
784    fn rejects_epilogue_after_close() {
785        block_on(async {
786            let mut mp = parser(b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundary--\r\nepilogue");
787            let mut part = mp.next_part().await.unwrap().unwrap();
788            let _ = drain_part(&mut part).await;
789            let err = mp.next_part().await.unwrap_err();
790            assert!(matches!(err, Error::StreamPartNotLast));
791        });
792    }
793
794    #[test]
795    fn rejects_missing_first_boundary() {
796        block_on(async {
797            let mut mp = parser(b"not multipart");
798            let err = mp.next_part().await.unwrap_err();
799            assert!(matches!(err, Error::InvalidFormat));
800        });
801    }
802
803    #[test]
804    fn enforces_header_size_limit() {
805        block_on(async {
806            let mut mp = Multipart::new(
807                stream::iter([Ok::<Bytes, std::io::Error>(Bytes::from_static(b"--boundary\r\nX: y\r\n\r\n"))])
808                    .map(|item| item.map_err(Error::stream_read_failed)),
809                &Boundary::new(b"boundary").unwrap(),
810                4,
811            );
812            let mut part = mp.next_part().await.unwrap().unwrap();
813            let err = part.next_header().await.unwrap_err();
814            assert!(matches!(err, Error::HeaderSizeExceeded { limit: 4 }));
815        });
816    }
817
818    async fn collect_taken<St>(mut stream: St) -> Result<Vec<u8>, Error>
819    where
820        St: Stream<Item = Result<Bytes, Error>> + Unpin,
821    {
822        let mut data = Vec::new();
823        while let Some(item) = futures_util::StreamExt::next(&mut stream).await {
824            data.extend_from_slice(&item?);
825        }
826        Ok(data)
827    }
828
829    #[test]
830    fn take_data_stream_yields_data_and_validates_trailer() {
831        block_on(async {
832            let mut mp = parser(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--\r\n");
833            let mut part = mp.next_part().await.unwrap().unwrap();
834            while part.next_header().await.unwrap().is_some() {}
835            let mut stream = part.take_data_stream().unwrap();
836            let snapshot = stream.multipart_consumed();
837            assert!(snapshot > 0);
838            let data = collect_taken(&mut stream).await.unwrap();
839            assert_eq!(data, b"hello");
840            let final_stream = stream.into_final();
841            assert!(collect_taken(final_stream).await.is_ok());
842            assert!(matches!(mp.next_part().await, Err(Error::StreamAlreadyTaken)));
843        });
844    }
845
846    #[test]
847    fn take_data_stream_rejects_epilogue() {
848        block_on(async {
849            let mut mp = parser(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--\r\nepilogue");
850            let mut part = mp.next_part().await.unwrap().unwrap();
851            while part.next_header().await.unwrap().is_some() {}
852            let stream = part.take_data_stream().unwrap();
853            let err = collect_taken(stream.into_final()).await.unwrap_err();
854            assert!(matches!(err, Error::StreamPartNotLast));
855        });
856    }
857
858    #[test]
859    fn take_data_stream_rejects_second_part() {
860        block_on(async {
861            let mut mp = parser(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary\r\nX: z\r\n\r\nworld\r\n--boundary--\r\n");
862            let mut part = mp.next_part().await.unwrap().unwrap();
863            while part.next_header().await.unwrap().is_some() {}
864            let stream = part.take_data_stream().unwrap();
865            let err = collect_taken(stream.into_final()).await.unwrap_err();
866            assert!(matches!(err, Error::StreamPartNotLast));
867        });
868    }
869
870    #[test]
871    fn take_data_stream_reports_incomplete_trailer() {
872        block_on(async {
873            let mut mp = parser(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--");
874            let mut part = mp.next_part().await.unwrap().unwrap();
875            while part.next_header().await.unwrap().is_some() {}
876            let stream = part.take_data_stream().unwrap();
877            let err = collect_taken(stream.into_final()).await.unwrap_err();
878            assert!(matches!(err, Error::IncompleteStreamPart));
879        });
880    }
881
882    /// The precondition is that the header block has been parsed, not that every
883    /// header has been yielded: dropping the unread ones is allowed and covered
884    /// in `part.rs`. Without any `next_header` call at all the take is refused.
885    #[test]
886    fn take_data_stream_requires_a_parsed_header_block() {
887        block_on(async {
888            let mut mp = parser(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--\r\n");
889            let part = mp.next_part().await.unwrap().unwrap();
890            assert!(matches!(part.take_data_stream(), Err(Error::InvalidFormat)));
891        });
892    }
893}
894
895#[cfg(test)]
896#[allow(
897    clippy::expect_used,
898    clippy::indexing_slicing,
899    clippy::panic,
900    clippy::unreachable,
901    clippy::unwrap_used
902)]
903mod coverage_tests {
904    use super::*;
905
906    use crate::delimiter::make_delimiter;
907    use crate::header::MAX_HEADERS;
908
909    use std::io;
910
911    use futures::executor::block_on;
912    use futures_util::StreamExt;
913    use futures_util::stream;
914
915    fn parser_from_chunks(
916        chunks: Vec<Result<&'static [u8], io::Error>>,
917    ) -> Multipart<impl Stream<Item = Result<Bytes, Error>> + Send + Sync> {
918        Multipart::new(
919            stream::iter(
920                chunks
921                    .into_iter()
922                    .map(|item| item.map(Bytes::from_static).map_err(Error::stream_read_failed)),
923            ),
924            &Boundary::new(b"boundary").unwrap(),
925            4096,
926        )
927    }
928
929    fn ok_chunks(chunks: Vec<&'static [u8]>) -> Multipart<impl Stream<Item = Result<Bytes, Error>> + Send + Sync> {
930        parser_from_chunks(chunks.into_iter().map(Ok).collect())
931    }
932
933    async fn collect_taken<St>(mut stream: St) -> Result<Vec<u8>, Error>
934    where
935        St: Stream<Item = Result<Bytes, Error>> + Unpin,
936    {
937        let mut data = Vec::new();
938        while let Some(item) = futures_util::StreamExt::next(&mut stream).await {
939            data.extend_from_slice(&item?);
940        }
941        Ok(data)
942    }
943
944    #[test]
945    fn next_part_drives_closing_state() {
946        block_on(async {
947            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundary--\r\n")]);
948            let mut part = mp.next_part().await.unwrap().unwrap();
949            while part.next_data().await.unwrap().is_some() {}
950            let _ = part;
951            assert!(mp.next_part().await.unwrap().is_none());
952        });
953    }
954
955    #[test]
956    fn closing_trailer_error_paths() {
957        block_on(async {
958            for body in [
959                &b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundary-x\r\n"[..],
960                b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundaryX\r\n",
961                b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundary--x\r\n",
962            ] {
963                let mut mp = parser_from_chunks(vec![Ok(body)]);
964                let mut part = mp.next_part().await.unwrap().unwrap();
965                while part.next_data().await.unwrap().is_some() {}
966                let _ = part;
967                assert!(matches!(mp.next_part().await, Err(Error::InvalidFormat)), "{body:?}");
968            }
969
970            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\rx")]);
971            assert!(matches!(mp.next_part().await, Err(Error::InvalidFormat)));
972        });
973    }
974
975    #[test]
976    fn closing_poll_sees_separate_epilogue_and_errors() {
977        block_on(async {
978            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundary--\r\n"), Ok(b"epilogue")]);
979            let mut part = mp.next_part().await.unwrap().unwrap();
980            while part.next_data().await.unwrap().is_some() {}
981            let _ = part;
982            assert!(matches!(mp.next_part().await, Err(Error::StreamPartNotLast)));
983
984            let mut mp = parser_from_chunks(vec![
985                Ok(b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundary--\r\n"),
986                Err(io::Error::other("boom")),
987            ]);
988            let mut part = mp.next_part().await.unwrap().unwrap();
989            while part.next_data().await.unwrap().is_some() {}
990            let _ = part;
991            assert!(matches!(mp.next_part().await, Err(Error::StreamReadFailed(_))));
992        });
993    }
994
995    #[test]
996    fn first_boundary_ensure_buf_cross_chunk_and_eof() {
997        block_on(async {
998            let mut mp = ok_chunks(vec![b"--boundary", b"\r\nX: y\r\n\r\ndata\r\n--boundary--\r\n"]);
999            let mut part = mp.next_part().await.unwrap().unwrap();
1000            let mut data = Vec::new();
1001            while let Some(chunk) = part.next_data().await.unwrap() {
1002                data.extend_from_slice(&chunk);
1003            }
1004            assert_eq!(data, b"data");
1005
1006            let mut mp = ok_chunks(vec![b"--boundary"]);
1007            assert!(matches!(mp.next_part().await, Err(Error::IncompleteStream)));
1008        });
1009    }
1010
1011    #[test]
1012    fn next_header_after_data_returns_none() {
1013        block_on(async {
1014            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\ndata\r\n--boundary--\r\n")]);
1015            let mut part = mp.next_part().await.unwrap().unwrap();
1016            while part.next_header().await.unwrap().is_some() {}
1017            let _ = part.next_data().await.unwrap();
1018            assert!(part.next_header().await.unwrap().is_none());
1019        });
1020    }
1021
1022    #[test]
1023    fn next_data_direct_chunk_paths() {
1024        block_on(async {
1025            let mut mp = ok_chunks(vec![
1026                b"--boundary\r\n\r\n",
1027                b"abcdefghijklmnopqrstuvwxyz0123456789",
1028                b"more-data\r\n--boundary--\r\n",
1029            ]);
1030            let mut part = mp.next_part().await.unwrap().unwrap();
1031            let mut data = Vec::new();
1032            while let Some(chunk) = part.next_data().await.unwrap() {
1033                data.extend_from_slice(&chunk);
1034            }
1035            assert!(data.starts_with(b"abcdefghijklmnopqrstuvwxyz0123456789more-data"));
1036
1037            let mut mp = ok_chunks(vec![b"--boundary\r\n\r\n", b"\r\n--boundary\r\nX: y\r\n\r\nz\r\n--boundary--\r\n"]);
1038            let mut part = mp.next_part().await.unwrap().unwrap();
1039            // An empty part yields no data item at all: the delimiter is already
1040            // at the front, so there is nothing to hand out.
1041            assert!(part.next_data().await.unwrap().is_none());
1042
1043            let mut mp = ok_chunks(vec![b"--boundary\r\nX: y\r\n\r\n", b"abc"]);
1044            let mut part = mp.next_part().await.unwrap().unwrap();
1045            while part.next_header().await.unwrap().is_some() {}
1046            assert!(matches!(part.next_data().await, Err(Error::IncompleteStream)));
1047
1048            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\n"), Err(io::Error::other("boom"))]);
1049            let mut part = mp.next_part().await.unwrap().unwrap();
1050            while part.next_header().await.unwrap().is_some() {}
1051            assert!(matches!(part.next_data().await, Err(Error::StreamReadFailed(_))));
1052        });
1053    }
1054
1055    #[test]
1056    fn double_take_is_rejected() {
1057        block_on(async {
1058            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--\r\n")]);
1059            let mut part = mp.next_part().await.unwrap().unwrap();
1060            while part.next_header().await.unwrap().is_some() {}
1061            let _ = part.take_data_stream().unwrap();
1062            mp.state = State::ReadingPartData;
1063            mp.buffer = None;
1064            assert!(matches!(mp.take_data_stream(), Err(Error::StreamAlreadyTaken)));
1065        });
1066    }
1067
1068    #[test]
1069    fn debug_impls_render() {
1070        block_on(async {
1071            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--\r\n")]);
1072            let mut part = mp.next_part().await.unwrap().unwrap();
1073            assert!(format!("{part:?}").contains("Part"));
1074            while part.next_header().await.unwrap().is_some() {}
1075            let stream = part.take_data_stream().unwrap();
1076            assert!(format!("{stream:?}").contains("PartDataStream"));
1077            assert_eq!(stream.size_hint(), (0, None));
1078        });
1079    }
1080
1081    #[test]
1082    fn part_data_stream_poll_paths() {
1083        block_on(async {
1084            let mut mp = ok_chunks(vec![
1085                b"--boundary\r\nX: y\r\n\r\n",
1086                b"abcdefghijklmnopqrstuvwxyz0123456789",
1087                b"rest\r\n--boundary--\r\n",
1088            ]);
1089            let mut part = mp.next_part().await.unwrap().unwrap();
1090            while part.next_header().await.unwrap().is_some() {}
1091            let stream = part.take_data_stream().unwrap();
1092            let data = collect_taken(stream).await.unwrap();
1093            assert!(data.starts_with(b"abcdefghijklmnopqrstuvwxyz0123456789rest"));
1094
1095            let mut mp = ok_chunks(vec![b"--boundary\r\nX: y\r\n\r\n", b"\r\n--boundary--\r\n"]);
1096            let mut part = mp.next_part().await.unwrap().unwrap();
1097            while part.next_header().await.unwrap().is_some() {}
1098            let stream = part.take_data_stream().unwrap();
1099            let mut chunks = 0;
1100            let mut stream = Box::pin(stream);
1101            while let Some(item) = futures_util::StreamExt::next(&mut stream).await {
1102                item.unwrap();
1103                chunks += 1;
1104            }
1105            assert_eq!(chunks, 0);
1106
1107            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\n"), Err(io::Error::other("boom"))]);
1108            let mut part = mp.next_part().await.unwrap().unwrap();
1109            while part.next_header().await.unwrap().is_some() {}
1110            let stream = part.take_data_stream().unwrap();
1111            assert!(matches!(collect_taken(stream.into_final()).await, Err(Error::StreamReadFailed(_))));
1112
1113            let mut mp = ok_chunks(vec![b"--boundary\r\nX: y\r\n\r\nabc"]);
1114            let mut part = mp.next_part().await.unwrap().unwrap();
1115            while part.next_header().await.unwrap().is_some() {}
1116            let stream = part.take_data_stream().unwrap();
1117            assert!(matches!(collect_taken(stream).await, Err(Error::IncompleteStreamPart)));
1118        });
1119    }
1120
1121    #[test]
1122    fn part_data_stream_trailer_paths() {
1123        block_on(async {
1124            for body in [
1125                &b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary-x\r\n"[..],
1126                b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--x\r\n",
1127            ] {
1128                let mut mp = parser_from_chunks(vec![Ok(body)]);
1129                let mut part = mp.next_part().await.unwrap().unwrap();
1130                while part.next_header().await.unwrap().is_some() {}
1131                let stream = part.take_data_stream().unwrap();
1132                assert!(matches!(collect_taken(stream.into_final()).await, Err(Error::InvalidFormat)), "{body:?}");
1133            }
1134
1135            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary-- \t\r\n")]);
1136            let mut part = mp.next_part().await.unwrap().unwrap();
1137            while part.next_header().await.unwrap().is_some() {}
1138            let stream = part.take_data_stream().unwrap();
1139            assert_eq!(collect_taken(stream.into_final()).await.unwrap(), b"hello");
1140
1141            let mut mp = parser_from_chunks(vec![Ok(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--\r\n"), Ok(b"epilogue")]);
1142            let mut part = mp.next_part().await.unwrap().unwrap();
1143            while part.next_header().await.unwrap().is_some() {}
1144            let stream = part.take_data_stream().unwrap();
1145            assert!(matches!(collect_taken(stream.into_final()).await, Err(Error::StreamPartNotLast)));
1146
1147            let mut mp = parser_from_chunks(vec![
1148                Ok(b"--boundary\r\nX: y\r\n\r\nhello\r\n--boundary--\r\n"),
1149                Err(io::Error::other("boom")),
1150            ]);
1151            let mut part = mp.next_part().await.unwrap().unwrap();
1152            while part.next_header().await.unwrap().is_some() {}
1153            let stream = part.take_data_stream().unwrap();
1154            assert!(matches!(collect_taken(stream.into_final()).await, Err(Error::StreamReadFailed(_))));
1155        });
1156    }
1157
1158    #[test]
1159    fn next_data_yields_buffered_data_prefix() {
1160        block_on(async {
1161            let mut mp = ok_chunks(vec![b"--boundary\r\nX: y\r\n\r\n0123456789abcdef", b"rest\r\n--boundary--\r\n"]);
1162            let mut part = mp.next_part().await.unwrap().unwrap();
1163            while part.next_header().await.unwrap().is_some() {}
1164            let first = part.next_data().await.unwrap().unwrap();
1165            assert!(!first.is_empty());
1166        });
1167    }
1168
1169    #[test]
1170    fn data_chunk_exactly_keep_is_buffered_not_split() {
1171        block_on(async {
1172            let delimiter_len = make_delimiter(b"boundary").len();
1173            let keep = delimiter_len - 1;
1174            let prefix = vec![b'x'; keep];
1175            let mut chunks = vec![b"--boundary\r\n\r\n".to_vec()];
1176            chunks.push(prefix);
1177            chunks.push(b"tail\r\n--boundary--\r\n".to_vec());
1178            let items = chunks
1179                .into_iter()
1180                .map(|chunk| Ok::<Bytes, std::io::Error>(Bytes::from(chunk)))
1181                .collect::<Vec<_>>();
1182            let mut multipart = Multipart::new(
1183                stream::iter(items).map(|item| item.map_err(Error::stream_read_failed)),
1184                &Boundary::new(b"boundary").unwrap(),
1185                4096,
1186            );
1187            let mut part = multipart.next_part().await.unwrap().unwrap();
1188            while part.next_header().await.unwrap().is_some() {}
1189            let mut data_chunks = 0;
1190            while part.next_data().await.unwrap().is_some() {
1191                data_chunks += 1;
1192            }
1193            assert_eq!(data_chunks, 1);
1194
1195            let delimiter_len = make_delimiter(b"boundary").len();
1196            let keep = delimiter_len - 1;
1197            let prefix = vec![b'x'; keep];
1198            let mut chunks = vec![b"--boundary\r\nContent-Disposition: form-data; name=\"file\"\r\n\r\n".to_vec()];
1199            chunks.push(prefix);
1200            chunks.push(b"tail\r\n--boundary--\r\n".to_vec());
1201            let items = chunks
1202                .into_iter()
1203                .map(|chunk| Ok::<Bytes, std::io::Error>(Bytes::from(chunk)))
1204                .collect::<Vec<_>>();
1205            let mut multipart = Multipart::new(
1206                stream::iter(items).map(|item| item.map_err(Error::stream_read_failed)),
1207                &Boundary::new(b"boundary").unwrap(),
1208                4096,
1209            );
1210            let mut part = multipart.next_part().await.unwrap().unwrap();
1211            while part.next_header().await.unwrap().is_some() {}
1212            let mut stream = Box::pin(part.take_data_stream().unwrap());
1213            let mut data_chunks = 0;
1214            while let Some(item) = futures_util::StreamExt::next(&mut stream).await {
1215                item.unwrap();
1216                data_chunks += 1;
1217            }
1218            assert_eq!(data_chunks, 1);
1219        });
1220    }
1221
1222    /// A part carrying more fields than [`MAX_HEADERS`] is still accepted: the
1223    /// fallback keeps the first fields, and their names and values read back
1224    /// exactly like the ones the httparse path produced.
1225    #[test]
1226    fn fallback_headers_keep_names_and_values() {
1227        block_on(async {
1228            let mut body = Vec::new();
1229            body.extend_from_slice(b"--boundary\r\n");
1230            for idx in 0..33 {
1231                body.extend_from_slice(format!("X-{idx}: value-{idx}\r\n").as_bytes());
1232            }
1233            body.extend_from_slice(b"\r\ndata\r\n--boundary--\r\n");
1234            let mut multipart = Multipart::new(
1235                stream::iter([Ok::<Bytes, std::io::Error>(Bytes::from(body))])
1236                    .map(|item| item.map_err(Error::stream_read_failed)),
1237                &Boundary::new(b"boundary").unwrap(),
1238                8192,
1239            );
1240            let mut part = multipart.next_part().await.unwrap().unwrap();
1241            let mut count = 0;
1242            while let Some(header) = part.next_header().await.unwrap() {
1243                assert_eq!(header.name, format!("X-{count}"));
1244                assert_eq!(header.value, format!("value-{count}").as_bytes());
1245                count += 1;
1246            }
1247            assert_eq!(count, MAX_HEADERS);
1248        });
1249    }
1250
1251    /// F31: `search_data` only switches to its adaptive tail for haystacks
1252    /// above `ADAPTIVE_TAIL_THRESHOLD`, which is the common case for real
1253    /// streams. A chunk boundary that falls inside the delimiter must still
1254    /// keep the leading bytes back, for every split position.
1255    #[test]
1256    fn large_data_chunk_keeps_a_split_delimiter() {
1257        block_on(async {
1258            const BIG: usize = 5000;
1259
1260            let delimiter = b"\r\n--boundary";
1261            for split in 1..delimiter.len() {
1262                let mut first = b"--boundary\r\nX: y\r\n\r\n".to_vec();
1263                first.extend(std::iter::repeat_n(b'x', BIG));
1264                first.extend_from_slice(&delimiter[..split]);
1265                let mut second = delimiter[split..].to_vec();
1266                second.extend_from_slice(b"--\r\n");
1267
1268                let mut mp = Multipart::new(
1269                    stream::iter([Ok::<Bytes, std::io::Error>(Bytes::from(first)), Ok(Bytes::from(second))])
1270                        .map(|item| item.map_err(Error::stream_read_failed)),
1271                    &Boundary::new(b"boundary").unwrap(),
1272                    4096,
1273                );
1274                let mut part = mp.next_part().await.unwrap().unwrap();
1275                while part.next_header().await.unwrap().is_some() {}
1276                let mut stream = part.take_data_stream().unwrap();
1277
1278                let data = collect_taken(&mut stream).await.unwrap();
1279                assert_eq!(data.len(), BIG, "split={split}");
1280                assert!(data.iter().all(|byte| *byte == b'x'), "split={split}");
1281
1282                let final_stream = stream.into_final();
1283                assert!(collect_taken(final_stream).await.is_ok(), "split={split}");
1284            }
1285        });
1286    }
1287
1288    /// Body with two parts, used by the poll-path tests below.
1289    const TWO_PARTS: &[u8] = b"--boundary\r\nX: a\r\n\r\nalpha\r\n--boundary\r\nX: b\r\n\r\nbeta\r\n--boundary--\r\n";
1290
1291    /// Consumes a parser through the async API, propagating every error.
1292    async fn drain_all<S>(mp: &mut Multipart<S>) -> Result<Vec<(Vec<(String, Vec<u8>)>, Vec<u8>)>, Error>
1293    where
1294        S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
1295    {
1296        let mut parts = Vec::new();
1297        while let Some(mut part) = mp.next_part().await? {
1298            let mut headers = Vec::new();
1299            while let Some(header) = part.next_header().await? {
1300                headers.push((header.name.to_string(), header.value.to_vec()));
1301            }
1302            let mut data = Vec::new();
1303            while let Some(chunk) = part.next_data().await? {
1304                data.extend_from_slice(&chunk);
1305            }
1306            parts.push((headers, data));
1307        }
1308        Ok(parts)
1309    }
1310
1311    /// One part as `(headers, data)`; the parsed form used by the poll tests.
1312    type ParsedPart = (Vec<(String, Vec<u8>)>, Vec<u8>);
1313
1314    /// The same walk through the poll API.
1315    fn drain_all_poll<S>(mp: &mut Multipart<S>, cx: &mut Context<'_>) -> Vec<ParsedPart>
1316    where
1317        S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
1318    {
1319        let mut parts = Vec::new();
1320        loop {
1321            match mp.poll_next_part(cx) {
1322                // `Pending` only needs another poll: the waker already fired.
1323                Poll::Pending => {}
1324                Poll::Ready(Ok(Some(mut part))) => parts.push(drain_part_poll(&mut part, cx)),
1325                Poll::Ready(Ok(None)) => break,
1326                Poll::Ready(Err(err)) => panic!("unexpected error: {err}"),
1327            }
1328        }
1329        parts
1330    }
1331
1332    fn drain_part_poll<S>(part: &mut Part<'_, S>, cx: &mut Context<'_>) -> ParsedPart
1333    where
1334        S: Stream<Item = Result<Bytes, Error>> + Send + Unpin,
1335    {
1336        let mut headers = Vec::new();
1337        loop {
1338            match part.poll_next_header(cx) {
1339                Poll::Pending => {}
1340                Poll::Ready(Ok(Some(header))) => headers.push((header.name.to_string(), header.value.to_vec())),
1341                Poll::Ready(Ok(None)) => break,
1342                Poll::Ready(Err(err)) => panic!("unexpected header error: {err}"),
1343            }
1344        }
1345        let mut data = Vec::new();
1346        loop {
1347            match part.poll_next_data(cx) {
1348                Poll::Pending => {}
1349                Poll::Ready(Ok(Some(chunk))) => data.extend_from_slice(&chunk),
1350                Poll::Ready(Ok(None)) => break,
1351                Poll::Ready(Err(err)) => panic!("unexpected data error: {err}"),
1352            }
1353        }
1354        (headers, data)
1355    }
1356
1357    /// Yields `chunks`, going `Pending` once before the chunk at index
1358    /// `pending_before` (waking immediately).
1359    fn pending_stream(
1360        chunks: Vec<Result<Bytes, Error>>,
1361        pending_before: usize,
1362    ) -> impl Stream<Item = Result<Bytes, Error>> + Send + Sync {
1363        let mut chunks = chunks.into_iter();
1364        let mut yielded = 0usize;
1365        stream::poll_fn(move |cx| {
1366            if yielded == pending_before {
1367                yielded += 1;
1368                cx.waker().wake_by_ref();
1369                return Poll::Pending;
1370            }
1371            yielded += 1;
1372            Poll::Ready(chunks.next())
1373        })
1374    }
1375
1376    /// F32a: the public poll entry point walks the same state machine as the
1377    /// async one, including its terminal arms and a sticky `Done`.
1378    #[test]
1379    fn poll_next_part_matches_the_async_path() {
1380        let reference = block_on(async {
1381            let mut mp = ok_chunks(vec![TWO_PARTS]);
1382            drain_all(&mut mp).await.unwrap()
1383        });
1384        assert_eq!(reference.len(), 2);
1385
1386        let mut mp = ok_chunks(vec![TWO_PARTS]);
1387        let waker = futures::task::noop_waker();
1388        let mut cx = Context::from_waker(&waker);
1389        assert_eq!(drain_all_poll(&mut mp, &mut cx), reference);
1390
1391        // `Done` is sticky: polling again must not restart or error.
1392        assert!(matches!(mp.poll_next_part(&mut cx), Poll::Ready(Ok(None))));
1393        assert!(matches!(mp.poll_next_part(&mut cx), Poll::Ready(Ok(None))));
1394    }
1395
1396    /// F45: driving the poll API and dropping a part without reading its data
1397    /// must skip that data and still yield every following part. The async
1398    /// driver is covered by `skips_unread_part_data_when_dropped`; the poll
1399    /// driver's skip arm — the state between "part handed out" and "data
1400    /// drained" — had no test at all.
1401    #[test]
1402    fn poll_next_part_skips_unread_part_data() {
1403        let reference = block_on(async {
1404            let mut mp = ok_chunks(vec![TWO_PARTS]);
1405            drain_all(&mut mp).await.unwrap()
1406        });
1407        assert_eq!(reference.len(), 2);
1408
1409        let mut mp = ok_chunks(vec![TWO_PARTS]);
1410        let waker = futures::task::noop_waker();
1411        let mut cx = Context::from_waker(&waker);
1412
1413        let mut headers_seen = Vec::new();
1414        loop {
1415            match mp.poll_next_part(&mut cx) {
1416                Poll::Pending => {}
1417                Poll::Ready(Ok(Some(mut part))) => {
1418                    let mut headers = Vec::new();
1419                    loop {
1420                        match part.poll_next_header(&mut cx) {
1421                            Poll::Pending => {}
1422                            Poll::Ready(Ok(Some(header))) => headers.push((header.name.to_string(), header.value.to_vec())),
1423                            Poll::Ready(Ok(None)) => break,
1424                            Poll::Ready(Err(err)) => panic!("unexpected header error: {err}"),
1425                        }
1426                    }
1427                    // The part is dropped here without reading any data, so the
1428                    // next poll skips it.
1429                    headers_seen.push(headers);
1430                }
1431                Poll::Ready(Ok(None)) => break,
1432                Poll::Ready(Err(err)) => panic!("unexpected error: {err}"),
1433            }
1434        }
1435
1436        let expected: Vec<_> = reference.into_iter().map(|(headers, _data)| headers).collect();
1437        assert_eq!(headers_seen, expected);
1438    }
1439
1440    /// F46: skipping unread data must surface a truncated part instead of
1441    /// reporting the end of the stream — on both drivers. The existing
1442    /// truncation sweep drives the parts it reads, not the ones it skips.
1443    #[test]
1444    fn skipping_unread_data_reports_a_truncated_part() {
1445        // Cut the closing delimiter's tail, so the second part's data never
1446        // reaches a delimiter.
1447        let truncated = &TWO_PARTS[..TWO_PARTS.len() - 7];
1448
1449        let async_err = block_on(async {
1450            let mut mp = ok_chunks(vec![truncated]);
1451            for _ in 0..2 {
1452                let mut part = mp.next_part().await.unwrap().unwrap();
1453                // The headers have to be read: until the data phase is entered,
1454                // the same part would be handed out again. The part then goes
1455                // out of scope without its data being read, so it is skipped.
1456                while part.next_header().await.unwrap().is_some() {}
1457            }
1458            mp.next_part().await.unwrap_err()
1459        });
1460        assert!(matches!(async_err, Error::IncompleteStream), "{async_err}");
1461
1462        let mut mp = ok_chunks(vec![truncated]);
1463        let waker = futures::task::noop_waker();
1464        let mut cx = Context::from_waker(&waker);
1465        let poll_err = loop {
1466            match mp.poll_next_part(&mut cx) {
1467                Poll::Pending => {}
1468                Poll::Ready(Ok(Some(mut part))) => while let Poll::Ready(Ok(Some(_))) = part.poll_next_header(&mut cx) {},
1469                Poll::Ready(Ok(None)) => panic!("a truncated body must not report the end"),
1470                Poll::Ready(Err(err)) => break err,
1471            }
1472        };
1473        assert!(matches!(poll_err, Error::IncompleteStream), "{poll_err}");
1474    }
1475
1476    /// F32a: `StreamTaken` is the other terminal arm — after the handover the
1477    /// poll entry point reports it instead of handing out another part.
1478    #[test]
1479    fn poll_next_part_reports_a_taken_stream() {
1480        let mut mp = ok_chunks(vec![b"--boundary\r\nX: a\r\n\r\nalpha\r\n--boundary--\r\n"]);
1481        let waker = futures::task::noop_waker();
1482        let mut cx = Context::from_waker(&waker);
1483
1484        let mut part = match mp.poll_next_part(&mut cx) {
1485            Poll::Ready(Ok(Some(part))) => part,
1486            other => panic!("expected a part, got {other:?}"),
1487        };
1488        while let Poll::Ready(Ok(Some(_))) = part.poll_next_header(&mut cx) {}
1489        let _stream = part.take_data_stream().unwrap();
1490
1491        assert!(matches!(mp.poll_next_part(&mut cx), Poll::Ready(Err(Error::StreamAlreadyTaken))));
1492    }
1493
1494    /// The stream has to be `Send + Unpin`, but not `Sync`: the parser polls it
1495    /// through `&mut`, so a stream that cannot be shared must still be accepted.
1496    /// A `Cell` is `Send` but not `Sync`, which is the smallest way to put that
1497    /// in a type.
1498    #[test]
1499    fn accepts_a_stream_that_is_send_but_not_sync() {
1500        struct SendOnlyStream {
1501            _not_sync: std::cell::Cell<u8>,
1502            chunks: std::vec::IntoIter<Result<Bytes, Error>>,
1503        }
1504
1505        impl Stream for SendOnlyStream {
1506            type Item = Result<Bytes, Error>;
1507
1508            fn poll_next(mut self: std::pin::Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1509                Poll::Ready(self.chunks.next())
1510            }
1511        }
1512
1513        let chunks: Vec<Result<Bytes, Error>> =
1514            vec![Ok(Bytes::from_static(b"--boundary\r\nX: a\r\n\r\nalpha\r\n--boundary--\r\n"))];
1515        let stream = SendOnlyStream {
1516            _not_sync: std::cell::Cell::new(0),
1517            chunks: chunks.into_iter(),
1518        };
1519        let boundary = Boundary::new(b"boundary").unwrap();
1520        let mut mp = Multipart::new(stream, &boundary, 4096);
1521        let parsed = block_on(drain_all(&mut mp)).unwrap();
1522        assert_eq!(parsed.len(), 1);
1523        assert_eq!(parsed[0].1, b"alpha");
1524    }
1525
1526    /// F32a: a `Pending` at any byte offset must resume into the same parse.
1527    /// The ready-only tests never suspend inside the boundary steps, so their
1528    /// `Pending` arms had no coverage at all.
1529    #[test]
1530    fn poll_next_part_resumes_after_pending_at_every_offset() {
1531        let reference = block_on(async {
1532            let mut mp = ok_chunks(vec![TWO_PARTS]);
1533            drain_all(&mut mp).await.unwrap()
1534        });
1535
1536        for pending_before in 0..=TWO_PARTS.len() {
1537            let chunks: Vec<Result<Bytes, Error>> = TWO_PARTS.chunks(1).map(|c| Ok(Bytes::copy_from_slice(c))).collect();
1538            let mut mp = Multipart::new(pending_stream(chunks, pending_before), &Boundary::new(b"boundary").unwrap(), 4096);
1539            let parsed = block_on(drain_all(&mut mp)).unwrap_or_else(|err| panic!("pending_before={pending_before}: {err}"));
1540            assert_eq!(parsed, reference, "pending_before={pending_before}");
1541        }
1542    }
1543
1544    /// F32a: truncating the body, or failing the stream, at any byte offset must
1545    /// surface an error instead of panicking or silently accepting the prefix —
1546    /// this is what covers the error arms of the poll steps.
1547    #[test]
1548    fn truncated_or_failing_streams_error_at_every_offset() {
1549        for cut in 0..TWO_PARTS.len() {
1550            let truncated = stream::iter(vec![Ok::<Bytes, Error>(Bytes::copy_from_slice(&TWO_PARTS[..cut]))]);
1551            let mut mp = Multipart::new(truncated, &Boundary::new(b"boundary").unwrap(), 4096);
1552            assert!(block_on(drain_all(&mut mp)).is_err(), "truncated at {cut}");
1553
1554            let failing = stream::iter(vec![
1555                Ok::<Bytes, Error>(Bytes::copy_from_slice(&TWO_PARTS[..cut])),
1556                Err(Error::stream_read_failed(io::Error::other("boom"))),
1557            ]);
1558            let mut mp = Multipart::new(failing, &Boundary::new(b"boundary").unwrap(), 4096);
1559            assert!(block_on(drain_all(&mut mp)).is_err(), "failing at {cut}");
1560        }
1561    }
1562
1563    /// F32b: a fresh chunk that carries data *and* the delimiter must yield the
1564    /// data prefix. With the delimiter at offset zero both branches of the
1565    /// `index == 0` split produce an empty chunk, so only this shape pins it.
1566    #[test]
1567    fn fresh_chunk_with_data_and_delimiter_yields_the_prefix() {
1568        block_on(async {
1569            let mut mp = ok_chunks(vec![b"--boundary\r\nX: y\r\n\r\n", b"hello\r\n--boundary--\r\n"]);
1570            let mut part = mp.next_part().await.unwrap().unwrap();
1571            while part.next_header().await.unwrap().is_some() {}
1572
1573            let first = part.next_data().await.unwrap().unwrap();
1574            assert_eq!(first.as_ref(), b"hello");
1575            assert!(part.next_data().await.unwrap().is_none());
1576        });
1577    }
1578}