Skip to main content

simoom_http1/
body.rs

1//! Incremental, zero-copy body decoding: Content-Length, chunked (RFC 9112 ยง7.1) and
2//! close-delimited.
3
4use crate::parse::check_trailers;
5use crate::{Error, Framing, Limits};
6use simoom_sys::scan::{find_byte, TCHAR};
7
8/// One step of body decoding.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum BodyEvent<'a> {
11    /// Body bytes, borrowed from the input.
12    Data(&'a [u8]),
13    /// The body is complete. For a chunked body with trailers this is the raw trailer section
14    /// (field lines and the final empty line), ready for [`parse_trailers`](crate::parse_trailers);
15    /// otherwise it is empty.
16    End(&'a [u8]),
17    /// All input was used; call again with more.
18    NeedMore,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22enum St {
23    Length,
24    Close,
25    Done,
26    Size0,
27    Size,
28    ExtPreSemi,
29    ExtNameStart,
30    ExtName,
31    ExtNameEnd,
32    ExtValStart,
33    ExtTok,
34    ExtQuoted,
35    ExtQuotedEsc,
36    ExtAfterQuote,
37    SizeLf,
38    Data,
39    DataCr,
40    DataLf,
41    Trailers,
42}
43
44/// Decodes one message body from successive pieces of input.
45///
46/// Each call returns how many input bytes it used and one event. Data comes back as slices of
47/// the input; chunk-size lines are consumed as they arrive (no buffering), and only an
48/// incomplete trailer section is left in the input (`consumed` stops before it) until it is
49/// whole, up to [`Limits::max_trailers`].
50#[derive(Clone, Debug)]
51pub struct BodyDecoder {
52    state: St,
53    remaining: u64,
54    received: u64,
55    limit: u64,
56    line: u32,
57    max_line: u32,
58    scanned: u32,
59    max_trailers: u32,
60    max_headers: u32,
61    max_header_line: u32,
62}
63
64#[inline]
65fn hexval(b: u8) -> Option<u64> {
66    match b {
67        b'0'..=b'9' => Some(u64::from(b - b'0')),
68        b'a'..=b'f' => Some(u64::from(b - b'a' + 10)),
69        b'A'..=b'F' => Some(u64::from(b - b'A' + 10)),
70        _ => None,
71    }
72}
73
74#[inline]
75fn ws(b: u8) -> bool {
76    b == b' ' || b == b'\t'
77}
78
79fn clamp(n: usize) -> u32 {
80    n.min(u32::MAX as usize) as u32
81}
82
83impl BodyDecoder {
84    /// A decoder for a body with this framing; `lim` gives the body, chunk-line and trailer
85    /// limits.
86    pub fn new(framing: Framing, lim: &Limits) -> Self {
87        let (state, remaining) = match framing {
88            Framing::None => (St::Done, 0),
89            Framing::Length(n) => (St::Length, n),
90            Framing::Chunked => (St::Size0, 0),
91            Framing::Close => (St::Close, 0),
92        };
93        BodyDecoder {
94            state,
95            remaining,
96            received: 0,
97            limit: lim.max_body,
98            line: 0,
99            max_line: clamp(lim.max_chunk_line),
100            scanned: 0,
101            max_trailers: clamp(lim.max_trailers),
102            max_headers: clamp(lim.max_headers),
103            max_header_line: clamp(lim.max_header_line),
104        }
105    }
106
107    /// True once the whole body (and any trailers) was decoded.
108    pub fn is_done(&self) -> bool {
109        self.state == St::Done
110    }
111
112    /// The connection closed: fine for a close-delimited body, an error inside any other.
113    pub fn eof(&mut self) -> Result<(), Error> {
114        match self.state {
115            St::Close | St::Done => {
116                self.state = St::Done;
117                Ok(())
118            }
119            _ => Err(Error::UnexpectedEof),
120        }
121    }
122
123    /// Decodes from `input`; returns the bytes used and what they produced.
124    pub fn decode<'a>(&mut self, input: &'a [u8]) -> Result<(usize, BodyEvent<'a>), Error> {
125        match self.state {
126            St::Done => return Ok((0, BodyEvent::End(&[]))),
127            St::Length => {
128                if self.remaining > self.limit {
129                    return Err(Error::BodyTooLarge);
130                }
131                if input.is_empty() {
132                    return Ok((0, BodyEvent::NeedMore));
133                }
134                let n = self.remaining.min(input.len() as u64) as usize;
135                self.remaining -= n as u64;
136                if self.remaining == 0 {
137                    self.state = St::Done;
138                }
139                return Ok((n, BodyEvent::Data(&input[..n])));
140            }
141            St::Close => {
142                if input.is_empty() {
143                    return Ok((0, BodyEvent::NeedMore));
144                }
145                self.received += input.len() as u64;
146                if self.received > self.limit {
147                    return Err(Error::BodyTooLarge);
148                }
149                return Ok((input.len(), BodyEvent::Data(input)));
150            }
151            _ => {}
152        }
153        let mut i = 0;
154        loop {
155            match self.state {
156                St::Data => {
157                    if i == input.len() {
158                        return Ok((i, BodyEvent::NeedMore));
159                    }
160                    let n = self.remaining.min((input.len() - i) as u64) as usize;
161                    self.remaining -= n as u64;
162                    if self.remaining == 0 {
163                        self.state = St::DataCr;
164                    }
165                    return Ok((i + n, BodyEvent::Data(&input[i..i + n])));
166                }
167                St::Trailers => return self.trailers(input, i),
168                St::Done => return Ok((i, BodyEvent::End(&[]))),
169                _ => {}
170            }
171            let Some(&b) = input.get(i) else { return Ok((i, BodyEvent::NeedMore)) };
172            i += 1;
173            self.step(b)?;
174        }
175    }
176
177    fn step(&mut self, b: u8) -> Result<(), Error> {
178        use St::*;
179        if !matches!(self.state, DataCr | DataLf) {
180            self.line += 1;
181            if self.line > self.max_line {
182                return Err(Error::ChunkLineTooLong);
183            }
184        }
185        let ext = Error::InvalidChunkExtension;
186        let tchar = TCHAR.contains(b);
187        self.state = match self.state {
188            Size0 => {
189                self.remaining = hexval(b).ok_or(Error::InvalidChunkSize)?;
190                Size
191            }
192            Size => match (hexval(b), b) {
193                (Some(v), _) => {
194                    if self.remaining >> 60 != 0 {
195                        return Err(Error::ChunkSizeOverflow);
196                    }
197                    self.remaining = self.remaining << 4 | v;
198                    Size
199                }
200                (_, b';') => ExtNameStart,
201                (_, b'\r') => SizeLf,
202                (_, b) if ws(b) => ExtPreSemi,
203                _ => return Err(Error::InvalidChunkSize),
204            },
205            ExtPreSemi => match b {
206                b';' => ExtNameStart,
207                b if ws(b) => ExtPreSemi,
208                _ => return Err(ext),
209            },
210            ExtNameStart => match b {
211                b if ws(b) => ExtNameStart,
212                _ if tchar => ExtName,
213                _ => return Err(ext),
214            },
215            ExtName => match b {
216                _ if tchar => ExtName,
217                b';' => ExtNameStart,
218                b'=' => ExtValStart,
219                b'\r' => SizeLf,
220                b if ws(b) => ExtNameEnd,
221                _ => return Err(ext),
222            },
223            ExtNameEnd => match b {
224                b'=' => ExtValStart,
225                b';' => ExtNameStart,
226                b if ws(b) => ExtNameEnd,
227                _ => return Err(ext),
228            },
229            ExtValStart => match b {
230                b'"' => ExtQuoted,
231                b if ws(b) => ExtValStart,
232                _ if tchar => ExtTok,
233                _ => return Err(ext),
234            },
235            ExtTok => match b {
236                _ if tchar => ExtTok,
237                b';' => ExtNameStart,
238                b'\r' => SizeLf,
239                b if ws(b) => ExtPreSemi,
240                _ => return Err(ext),
241            },
242            ExtQuoted => match b {
243                b'"' => ExtAfterQuote,
244                b'\\' => ExtQuotedEsc,
245                b'\t' | b' ' | 0x21 | 0x23..=0x5b | 0x5d..=0x7e | 0x80..=0xff => ExtQuoted,
246                _ => return Err(ext),
247            },
248            ExtQuotedEsc => match b {
249                b'\t' | 0x20..=0x7e | 0x80..=0xff => ExtQuoted,
250                _ => return Err(ext),
251            },
252            ExtAfterQuote => match b {
253                b';' => ExtNameStart,
254                b'\r' => SizeLf,
255                b if ws(b) => ExtPreSemi,
256                _ => return Err(ext),
257            },
258            SizeLf => {
259                if b != b'\n' {
260                    return Err(Error::BareCr);
261                }
262                self.line = 0;
263                if self.remaining == 0 {
264                    Trailers
265                } else {
266                    self.received = self.received.saturating_add(self.remaining);
267                    if self.received > self.limit {
268                        return Err(Error::BodyTooLarge);
269                    }
270                    Data
271                }
272            }
273            DataCr if b == b'\r' => DataLf,
274            DataLf if b == b'\n' => Size0,
275            DataCr | DataLf => return Err(Error::MissingChunkCrlf),
276            Length | Close | Done | Data | Trailers => unreachable!("not a chunk-line state"),
277        };
278        Ok(())
279    }
280
281    fn trailers<'a>(&mut self, input: &'a [u8], i: usize) -> Result<(usize, BodyEvent<'a>), Error> {
282        let rest = &input[i..];
283        match rest {
284            [] | [b'\r'] => return Ok((i, BodyEvent::NeedMore)),
285            [b'\r', b'\n', ..] => {
286                self.state = St::Done;
287                return Ok((i + 2, BodyEvent::End(&[])));
288            }
289            [b'\r', ..] => return Err(Error::BareCr),
290            _ => {}
291        }
292        let mut k = (self.scanned as usize).saturating_sub(3);
293        while let Some(off) = find_byte(&rest[k..], b'\n') {
294            let at = k + off;
295            if at >= 3 && &rest[at - 3..=at] == b"\r\n\r\n" {
296                let section = &rest[..=at];
297                let lim = Limits {
298                    max_headers: self.max_headers as usize,
299                    max_header_line: self.max_header_line as usize,
300                    max_trailers: self.max_trailers as usize,
301                    ..Limits::default()
302                };
303                check_trailers(section, &lim)?;
304                self.state = St::Done;
305                return Ok((i + section.len(), BodyEvent::End(section)));
306            }
307            k = at + 1;
308        }
309        if rest.len() > self.max_trailers as usize {
310            return Err(Error::TrailersTooLarge);
311        }
312        self.scanned = clamp(rest.len());
313        Ok((i, BodyEvent::NeedMore))
314    }
315}