Skip to main content

base_web_multipart/
parts.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::{Boundary, MultipartError, MultipartEvent, MultipartLimits, MultipartParser, PartHead};
5use std::fmt;
6use std::iter::FusedIterator;
7
8/// One part of a complete body: its header section and content, borrowed from the body.
9#[derive(Clone, Copy, PartialEq, Eq)]
10pub struct Part<'a> {
11    head: PartHead<'a>,
12    content: &'a [u8],
13}
14
15impl<'a> Part<'a> {
16    /// The header section.
17    pub fn head(&self) -> PartHead<'a> {
18        self.head
19    }
20
21    /// The content.
22    pub fn content(&self) -> &'a [u8] {
23        self.content
24    }
25}
26
27/// Shows the length of the content rather than its bytes, since content must not reach logs.
28impl fmt::Debug for Part<'_> {
29    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30        formatter
31            .debug_struct("Part")
32            .field("head", &self.head)
33            .field("content", &format_args!("<{} bytes>", self.content.len()))
34            .finish()
35    }
36}
37
38/// The parts of a complete body held in memory, in order, as [`parts`] produces them.
39#[derive(Clone)]
40pub struct Parts<'a> {
41    parser: MultipartParser,
42    body: &'a [u8],
43    position: usize,
44    failed: bool,
45}
46
47/// The parts of a complete body, with the default limits.
48///
49/// ```
50/// use base_web_multipart::{Boundary, ParameterRules, PartHeaders, parts};
51/// use base_message::FieldSection;
52///
53/// let body = b"--b\r\nContent-Disposition: form-data; name=\"a\"\r\n\r\none\r\n--b\r\nContent-Disposition: form-data; name=\"b\"\r\n\r\ntwo\r\n--b--";
54/// let boundary = Boundary::new("b")?;
55/// let mut fields = Vec::new();
56/// for part in parts(body, &boundary) {
57///     let part = part?;
58///     let headers = PartHeaders::interpret(part.head())?;
59///     let disposition = headers.content_disposition.ok_or("a form part has a disposition")?;
60///     let name = disposition.name(ParameterRules::FormData)?.ok_or("a form part has a name")?.into_owned();
61///     fields.push((name, part.content()));
62/// }
63/// assert_eq!(fields, [("a".to_owned(), &b"one"[..]), ("b".to_owned(), &b"two"[..])]);
64/// # Ok::<(), Box<dyn std::error::Error>>(())
65/// ```
66pub fn parts<'a>(body: &'a [u8], boundary: &Boundary) -> Parts<'a> {
67    Parts::with_limits(body, boundary, MultipartLimits::default())
68}
69
70impl<'a> Parts<'a> {
71    /// The parts of a complete body, with these limits.
72    pub fn with_limits(body: &'a [u8], boundary: &Boundary, limits: MultipartLimits) -> Self {
73        Self {
74            parser: MultipartParser::with_limits(boundary, limits),
75            body,
76            position: 0,
77            failed: false,
78        }
79    }
80
81    fn next_part(&mut self) -> Result<Option<Part<'a>>, MultipartError> {
82        let body = self.body;
83        let (mut head, mut content, mut start) = (None, self.position..self.position, None);
84        loop {
85            let parsed = self.parser.parse(&body[self.position..])?;
86            self.position += parsed.consumed;
87            match parsed.event {
88                MultipartEvent::Part(part) => {
89                    head = Some(part);
90                    content = self.position..self.position;
91                    start = None;
92                }
93                // Each piece ends where the parser stopped, and pieces of one part's content are adjacent in the body, so the content runs from the first piece's start to the last piece's end.
94                MultipartEvent::Content(piece) => {
95                    content = *start.get_or_insert(self.position - piece.len())..self.position
96                }
97                MultipartEvent::PartEnd => {
98                    return Ok(head.map(|head| Part {
99                        head,
100                        content: &body[content],
101                    }));
102                }
103                MultipartEvent::End => return Ok(None),
104                // The whole body was given, so more input will not arrive.
105                MultipartEvent::Incomplete => return self.parser.finish().and(Ok(None)),
106            }
107        }
108    }
109}
110
111/// Shows the position in the body and its length, since the body must not reach logs.
112impl fmt::Debug for Parts<'_> {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        formatter
115            .debug_struct("Parts")
116            .field("position", &self.position)
117            .field("length", &self.body.len())
118            .finish()
119    }
120}
121
122impl<'a> Iterator for Parts<'a> {
123    type Item = Result<Part<'a>, MultipartError>;
124
125    fn next(&mut self) -> Option<Self::Item> {
126        if self.failed {
127            return None;
128        }
129        let result = self.next_part().transpose();
130        self.failed = !matches!(result, Some(Ok(_)));
131        result
132    }
133}
134
135impl FusedIterator for Parts<'_> {}