1use crate::chars::{is_ows, is_token, is_valid_boundary, ows_trimmed_range, param, trim_ows};
4use crate::Error;
5
6pub const MAX_PADDING: usize = 8;
9
10const LOOKAHEAD_EXTRA: usize = 4 + MAX_PADDING + 2;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum Event<'i, 'p> {
17 PartStart,
19 Header {
22 name: &'p [u8],
24 value: &'p [u8],
27 },
28 HeadersEnd,
30 Data(&'i [u8]),
32 PartEnd,
34 Finished,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39enum State {
40 Preamble { at_start: bool },
41 Headers,
42 Body,
43 NewPart,
44 Finished,
45 Failed(Error),
46}
47
48#[derive(Debug, Clone)]
57pub struct Parser<'b, const LINE: usize = 512, const MAX_HEADERS: usize = 16> {
58 boundary: &'b [u8],
59 state: State,
60 line: [u8; LINE],
61 line_len: usize,
62 header_count: usize,
63}
64
65impl<'b, const LINE: usize, const MAX_HEADERS: usize> Parser<'b, LINE, MAX_HEADERS> {
66 pub fn new(boundary: &'b [u8]) -> Result<Self, Error> {
72 if !is_valid_boundary(boundary) {
73 return Err(Error::Boundary);
74 }
75 Ok(Self {
76 boundary,
77 state: State::Preamble { at_start: true },
78 line: [0; LINE],
79 line_len: 0,
80 header_count: 0,
81 })
82 }
83
84 pub fn from_content_type(content_type: &'b [u8]) -> Result<Self, Error> {
92 match boundary_param(content_type) {
93 Some(b) => Self::new(b),
94 None => Err(Error::Boundary),
95 }
96 }
97
98 #[must_use]
100 pub const fn boundary(&self) -> &'b [u8] {
101 self.boundary
102 }
103
104 #[must_use]
107 pub const fn min_buffer(&self) -> usize {
108 self.boundary.len().saturating_add(LOOKAHEAD_EXTRA)
109 }
110
111 #[must_use]
113 pub const fn is_finished(&self) -> bool {
114 matches!(self.state, State::Finished)
115 }
116
117 pub fn parse<'i>(
131 &mut self,
132 input: &'i [u8],
133 eof: bool,
134 ) -> Result<(usize, Option<Event<'i, '_>>), Error> {
135 let (consumed, ev) = self.step(input, eof)?;
136 Ok((consumed, ev.map(|ev| self.materialize(input, ev))))
137 }
138
139 pub(crate) fn step(&mut self, input: &[u8], eof: bool) -> Result<(usize, Option<Ev>), Error> {
142 let step = match self.state {
143 State::NewPart => {
144 self.state = State::Headers;
145 return Ok((0, Some(Ev::PartStart)));
146 }
147 State::Finished => return Ok((input.len(), Some(Ev::Finished))),
148 State::Failed(e) => Err(e),
149 _ if input.is_empty() && eof => Err(Error::UnexpectedEof),
150 _ if input.is_empty() => Ok((0, None)),
151 State::Preamble { at_start } => Ok(self.preamble(input, eof, at_start)),
152 State::Headers => self.headers(input),
153 State::Body => Ok(self.body(input, eof)),
154 };
155 step.inspect_err(|&e| self.state = State::Failed(e))
156 }
157
158 pub(crate) fn materialize<'i>(&self, input: &'i [u8], ev: Ev) -> Event<'i, '_> {
161 match ev {
162 Ev::PartStart => Event::PartStart,
163 Ev::HeadersEnd => Event::HeadersEnd,
164 Ev::PartEnd => Event::PartEnd,
165 Ev::Finished => Event::Finished,
166 Ev::Data { end } => Event::Data(input.get(..end).unwrap_or(input)),
167 Ev::Header {
168 name_end,
169 value_start,
170 value_end,
171 } => Event::Header {
172 name: self.line.get(..name_end).unwrap_or(&[]),
173 value: self.line.get(value_start..value_end).unwrap_or(&[]),
174 },
175 }
176 }
177
178 fn preamble(&mut self, input: &[u8], eof: bool, at_start: bool) -> (usize, Option<Ev>) {
180 let (consumed, ev) = match scan(input, eof, at_start, self.boundary) {
181 Found::Delim { at, len, close } => {
182 let (state, ev) = if close {
183 (State::Finished, Ev::Finished)
184 } else {
185 (State::Headers, Ev::PartStart)
186 };
187 self.state = state;
188 return (at.saturating_add(len), Some(ev));
189 }
190 Found::HeldBack { at } => (at, None),
191 Found::Nothing => (input.len(), None),
192 };
193 if consumed > 0 {
194 self.state = State::Preamble { at_start: false };
195 }
196 (consumed, ev)
197 }
198
199 fn headers(&mut self, input: &[u8]) -> Result<(usize, Option<Ev>), Error> {
202 let lf = input.iter().position(|&b| b == b'\n');
203 let take = lf.map_or(input.len(), |i| i.saturating_add(1));
204 let chunk = input.get(..take).unwrap_or(input);
205 let new_len = self.line_len.saturating_add(chunk.len());
206 if new_len > LINE {
207 return Err(Error::HeaderTooLong); }
209 for (dst, &src) in self.line.iter_mut().skip(self.line_len).zip(chunk) {
210 *dst = src;
211 }
212 self.line_len = new_len;
213 if lf.is_none() {
214 return Ok((take, None));
215 }
216 self.line_len = 0;
218 let line = self.line.get(..new_len).unwrap_or(&[]);
219 let Some(content) = line.strip_suffix(b"\r\n") else {
220 return Err(Error::Header); };
222 if content.is_empty() {
223 self.state = State::Body; self.header_count = 0;
225 return Ok((take, Some(Ev::HeadersEnd)));
226 }
227 self.header_count = self.header_count.saturating_add(1);
228 if self.header_count > MAX_HEADERS {
229 return Err(Error::TooManyHeaders); }
231 let Some(colon) = content.iter().position(|&b| b == b':') else {
232 return Err(Error::Header); };
234 if !is_token(content.get(..colon).unwrap_or(&[])) {
235 return Err(Error::Header); }
237 if content.contains(&b'\r') {
238 return Err(Error::Header); }
240 let value_from = colon.saturating_add(1);
241 let (start, end) = ows_trimmed_range(content.get(value_from..).unwrap_or(&[]));
242 Ok((
243 take,
244 Some(Ev::Header {
245 name_end: colon,
246 value_start: value_from.saturating_add(start),
247 value_end: value_from.saturating_add(end),
248 }),
249 ))
250 }
251
252 fn body(&mut self, input: &[u8], eof: bool) -> (usize, Option<Ev>) {
254 let end = match scan(input, eof, false, self.boundary) {
255 Found::Delim { at: 0, len, close } => {
256 self.state = if close {
257 State::Finished
258 } else {
259 State::NewPart
260 };
261 return (len, Some(Ev::PartEnd));
262 }
263 Found::Delim { at, .. } | Found::HeldBack { at } => at,
264 Found::Nothing => input.len(),
265 };
266 (end, (end > 0).then_some(Ev::Data { end }))
267 }
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273pub(crate) enum Ev {
274 PartStart,
275 Header {
276 name_end: usize,
277 value_start: usize,
278 value_end: usize,
279 },
280 HeadersEnd,
281 Data {
282 end: usize,
283 },
284 PartEnd,
285 Finished,
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
289enum Found {
290 Delim { at: usize, len: usize, close: bool },
292 HeldBack { at: usize },
294 Nothing,
296}
297
298fn classify(s: &[u8], lead: &[u8], boundary: &[u8]) -> Found {
301 let full = lead.len().saturating_add(boundary.len());
302 if !s
303 .iter()
304 .zip(lead.iter().chain(boundary))
305 .all(|(a, b)| a == b)
306 {
307 return Found::Nothing;
308 }
309 let Some(tail) = s.get(full..) else {
310 return Found::HeldBack { at: 0 };
311 };
312 let pad = tail
313 .iter()
314 .take(MAX_PADDING + 1)
315 .take_while(|&&b| is_ows(b))
316 .count();
317 let (len, close) = match (
318 tail.first(),
319 tail.get(1),
320 tail.get(pad),
321 tail.get(pad.saturating_add(1)),
322 ) {
323 (Some(b'-'), Some(b'-'), ..) => (2, true),
324 (None | Some(b'-'), None, ..) | (_, _, None | Some(b'\r'), None) if pad <= MAX_PADDING => {
325 return Found::HeldBack { at: 0 }
326 }
327 (Some(b'-'), ..) => return Found::Nothing,
328 (.., Some(b'\r'), Some(b'\n')) if pad <= MAX_PADDING => (pad.saturating_add(2), false),
329 _ => return Found::Nothing,
330 };
331 Found::Delim {
332 at: 0,
333 len: full.saturating_add(len),
334 close,
335 }
336}
337
338fn scan(input: &[u8], eof: bool, allow_d0: bool, boundary: &[u8]) -> Found {
341 let mut from = 0usize;
342 let mut try_d0 = allow_d0 && input.first() == Some(&b'-');
343 loop {
344 let (at, lead): (usize, &[u8]) = if try_d0 {
345 (0, b"--")
346 } else {
347 match input
348 .get(from..)
349 .and_then(|r| r.iter().position(|&b| b == b'\r'))
350 {
351 Some(off) => (from.saturating_add(off), b"\r\n--"),
352 None => return Found::Nothing,
353 }
354 };
355 try_d0 = false;
356 match classify(input.get(at..).unwrap_or(&[]), lead, boundary) {
357 Found::Delim { len, close, .. } => return Found::Delim { at, len, close },
358 Found::HeldBack { .. } if !eof => return Found::HeldBack { at },
359 _ => from = at.saturating_add(1),
360 }
361 }
362}
363
364fn boundary_param(ct: &[u8]) -> Option<&[u8]> {
366 let slash = ct.iter().position(|&b| b == b'/')?;
367 let (mtype, rest) = ct.split_at_checked(slash)?;
368 if !trim_ows(mtype).eq_ignore_ascii_case(b"multipart") {
369 return None;
370 }
371 let rest = rest.get(1..)?;
372 let semi = rest.iter().position(|&b| b == b';').unwrap_or(rest.len());
373 let (subtype, mut params) = rest.split_at_checked(semi)?;
374 if !is_token(trim_ows(subtype)) {
375 return None;
376 }
377 loop {
378 let (attr, value, quoted, rest) = param(params.strip_prefix(b";")?).ok()??;
379 if !quoted && !is_token(value) {
380 return None;
381 }
382 if attr.eq_ignore_ascii_case(b"boundary") {
383 return Some(value);
384 }
385 params = rest;
386 }
387}