actix_http/h1/
chunked.rs

1use std::{io, task::Poll};
2
3use bytes::{Buf as _, Bytes, BytesMut};
4use tracing::{debug, trace};
5
6macro_rules! byte (
7    ($rdr:ident) => ({
8        if $rdr.len() > 0 {
9            let b = $rdr[0];
10            $rdr.advance(1);
11            b
12        } else {
13            return Poll::Pending
14        }
15    })
16);
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub(super) enum ChunkedState {
20    Size,
21    SizeLws,
22    Extension,
23    SizeLf,
24    Body,
25    BodyCr,
26    BodyLf,
27    EndCr,
28    EndLf,
29    End,
30}
31
32impl ChunkedState {
33    pub(super) fn step(
34        &self,
35        body: &mut BytesMut,
36        size: &mut u64,
37        buf: &mut Option<Bytes>,
38    ) -> Poll<Result<ChunkedState, io::Error>> {
39        use self::ChunkedState::*;
40        match *self {
41            Size => ChunkedState::read_size(body, size),
42            SizeLws => ChunkedState::read_size_lws(body),
43            Extension => ChunkedState::read_extension(body),
44            SizeLf => ChunkedState::read_size_lf(body, *size),
45            Body => ChunkedState::read_body(body, size, buf),
46            BodyCr => ChunkedState::read_body_cr(body),
47            BodyLf => ChunkedState::read_body_lf(body),
48            EndCr => ChunkedState::read_end_cr(body),
49            EndLf => ChunkedState::read_end_lf(body),
50            End => Poll::Ready(Ok(ChunkedState::End)),
51        }
52    }
53
54    fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<Result<ChunkedState, io::Error>> {
55        let radix = 16;
56
57        let rem = match byte!(rdr) {
58            b @ b'0'..=b'9' => b - b'0',
59            b @ b'a'..=b'f' => b + 10 - b'a',
60            b @ b'A'..=b'F' => b + 10 - b'A',
61            b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
62            b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
63            b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
64            _ => {
65                return Poll::Ready(Err(io::Error::new(
66                    io::ErrorKind::InvalidInput,
67                    "Invalid chunk size line: Invalid Size",
68                )));
69            }
70        };
71
72        match size.checked_mul(radix) {
73            Some(n) => {
74                *size = n;
75                *size += rem as u64;
76
77                Poll::Ready(Ok(ChunkedState::Size))
78            }
79            None => {
80                debug!("chunk size would overflow u64");
81                Poll::Ready(Err(io::Error::new(
82                    io::ErrorKind::InvalidInput,
83                    "Invalid chunk size line: Size is too big",
84                )))
85            }
86        }
87    }
88
89    fn read_size_lws(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
90        match byte!(rdr) {
91            // LWS can follow the chunk size, but no more digits can come
92            b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)),
93            b';' => Poll::Ready(Ok(ChunkedState::Extension)),
94            b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
95            _ => Poll::Ready(Err(io::Error::new(
96                io::ErrorKind::InvalidInput,
97                "Invalid chunk size linear white space",
98            ))),
99        }
100    }
101    fn read_extension(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
102        match byte!(rdr) {
103            b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
104            // strictly 0x20 (space) should be disallowed but we don't parse quoted strings here
105            0x00..=0x08 | 0x0a..=0x1f | 0x7f => Poll::Ready(Err(io::Error::new(
106                io::ErrorKind::InvalidInput,
107                "Invalid character in chunk extension",
108            ))),
109            _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
110        }
111    }
112    fn read_size_lf(rdr: &mut BytesMut, size: u64) -> Poll<Result<ChunkedState, io::Error>> {
113        match byte!(rdr) {
114            b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
115            b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
116            _ => Poll::Ready(Err(io::Error::new(
117                io::ErrorKind::InvalidInput,
118                "Invalid chunk size LF",
119            ))),
120        }
121    }
122
123    fn read_body(
124        rdr: &mut BytesMut,
125        rem: &mut u64,
126        buf: &mut Option<Bytes>,
127    ) -> Poll<Result<ChunkedState, io::Error>> {
128        trace!("Chunked read, remaining={:?}", rem);
129
130        let len = rdr.len() as u64;
131        if len == 0 {
132            Poll::Ready(Ok(ChunkedState::Body))
133        } else {
134            let slice;
135            if *rem > len {
136                slice = rdr.split().freeze();
137                *rem -= len;
138            } else {
139                slice = rdr.split_to(*rem as usize).freeze();
140                *rem = 0;
141            }
142            *buf = Some(slice);
143            if *rem > 0 {
144                Poll::Ready(Ok(ChunkedState::Body))
145            } else {
146                Poll::Ready(Ok(ChunkedState::BodyCr))
147            }
148        }
149    }
150
151    fn read_body_cr(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
152        match byte!(rdr) {
153            b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)),
154            _ => Poll::Ready(Err(io::Error::new(
155                io::ErrorKind::InvalidInput,
156                "Invalid chunk body CR",
157            ))),
158        }
159    }
160    fn read_body_lf(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
161        match byte!(rdr) {
162            b'\n' => Poll::Ready(Ok(ChunkedState::Size)),
163            _ => Poll::Ready(Err(io::Error::new(
164                io::ErrorKind::InvalidInput,
165                "Invalid chunk body LF",
166            ))),
167        }
168    }
169    fn read_end_cr(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
170        match byte!(rdr) {
171            b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)),
172            _ => Poll::Ready(Err(io::Error::new(
173                io::ErrorKind::InvalidInput,
174                "Invalid chunk end CR",
175            ))),
176        }
177    }
178    fn read_end_lf(rdr: &mut BytesMut) -> Poll<Result<ChunkedState, io::Error>> {
179        match byte!(rdr) {
180            b'\n' => Poll::Ready(Ok(ChunkedState::End)),
181            _ => Poll::Ready(Err(io::Error::new(
182                io::ErrorKind::InvalidInput,
183                "Invalid chunk end LF",
184            ))),
185        }
186    }
187}
188
189#[cfg(test)]
190mod tests {
191    use actix_codec::Decoder as _;
192    use bytes::{Bytes, BytesMut};
193    use http::Method;
194
195    use crate::{
196        error::ParseError,
197        h1::decoder::{MessageDecoder, PayloadItem},
198        HttpMessage as _, Request,
199    };
200
201    macro_rules! parse_ready {
202        ($e:expr) => {{
203            match MessageDecoder::<Request>::default().decode($e) {
204                Ok(Some((msg, _))) => msg,
205                Ok(_) => unreachable!("Eof during parsing http request"),
206                Err(err) => unreachable!("Error during parsing http request: {:?}", err),
207            }
208        }};
209    }
210
211    macro_rules! expect_parse_err {
212        ($e:expr) => {{
213            match MessageDecoder::<Request>::default().decode($e) {
214                Err(err) => match err {
215                    ParseError::Io(_) => unreachable!("Parse error expected"),
216                    _ => {}
217                },
218                _ => unreachable!("Error expected"),
219            }
220        }};
221    }
222
223    #[test]
224    fn test_parse_chunked_payload_chunk_extension() {
225        let mut buf = BytesMut::from(
226            "GET /test HTTP/1.1\r\n\
227            transfer-encoding: chunked\r\n\
228            \r\n",
229        );
230
231        let mut reader = MessageDecoder::<Request>::default();
232        let (msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
233        let mut pl = pl.unwrap();
234        assert!(msg.chunked().unwrap());
235
236        buf.extend(b"4;test\r\ndata\r\n4\r\nline\r\n0\r\n\r\n"); // test: test\r\n\r\n")
237        let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk();
238        assert_eq!(chunk, Bytes::from_static(b"data"));
239        let chunk = pl.decode(&mut buf).unwrap().unwrap().chunk();
240        assert_eq!(chunk, Bytes::from_static(b"line"));
241        let msg = pl.decode(&mut buf).unwrap().unwrap();
242        assert!(msg.eof());
243    }
244
245    #[test]
246    fn test_request_chunked() {
247        let mut buf = BytesMut::from(
248            "GET /test HTTP/1.1\r\n\
249             transfer-encoding: chunked\r\n\r\n",
250        );
251        let req = parse_ready!(&mut buf);
252
253        if let Ok(val) = req.chunked() {
254            assert!(val);
255        } else {
256            unreachable!("Error");
257        }
258
259        // intentional typo in "chunked"
260        let mut buf = BytesMut::from(
261            "GET /test HTTP/1.1\r\n\
262             transfer-encoding: chnked\r\n\r\n",
263        );
264        expect_parse_err!(&mut buf);
265    }
266
267    #[test]
268    fn test_http_request_chunked_payload() {
269        let mut buf = BytesMut::from(
270            "GET /test HTTP/1.1\r\n\
271             transfer-encoding: chunked\r\n\r\n",
272        );
273        let mut reader = MessageDecoder::<Request>::default();
274        let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
275        let mut pl = pl.unwrap();
276        assert!(req.chunked().unwrap());
277
278        buf.extend(b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n");
279        assert_eq!(
280            pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(),
281            b"data"
282        );
283        assert_eq!(
284            pl.decode(&mut buf).unwrap().unwrap().chunk().as_ref(),
285            b"line"
286        );
287        assert!(pl.decode(&mut buf).unwrap().unwrap().eof());
288    }
289
290    #[test]
291    fn test_http_request_chunked_payload_and_next_message() {
292        let mut buf = BytesMut::from(
293            "GET /test HTTP/1.1\r\n\
294             transfer-encoding: chunked\r\n\r\n",
295        );
296        let mut reader = MessageDecoder::<Request>::default();
297        let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
298        let mut pl = pl.unwrap();
299        assert!(req.chunked().unwrap());
300
301        buf.extend(
302            b"4\r\ndata\r\n4\r\nline\r\n0\r\n\r\n\
303              POST /test2 HTTP/1.1\r\n\
304              transfer-encoding: chunked\r\n\r\n"
305                .iter(),
306        );
307        let msg = pl.decode(&mut buf).unwrap().unwrap();
308        assert_eq!(msg.chunk().as_ref(), b"data");
309        let msg = pl.decode(&mut buf).unwrap().unwrap();
310        assert_eq!(msg.chunk().as_ref(), b"line");
311        let msg = pl.decode(&mut buf).unwrap().unwrap();
312        assert!(msg.eof());
313
314        let (req, _) = reader.decode(&mut buf).unwrap().unwrap();
315        assert!(req.chunked().unwrap());
316        assert_eq!(*req.method(), Method::POST);
317        assert!(req.chunked().unwrap());
318    }
319
320    #[test]
321    fn test_http_request_chunked_payload_chunks() {
322        let mut buf = BytesMut::from(
323            "GET /test HTTP/1.1\r\n\
324             transfer-encoding: chunked\r\n\r\n",
325        );
326
327        let mut reader = MessageDecoder::<Request>::default();
328        let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
329        let mut pl = pl.unwrap();
330        assert!(req.chunked().unwrap());
331
332        buf.extend(b"4\r\n1111\r\n");
333        let msg = pl.decode(&mut buf).unwrap().unwrap();
334        assert_eq!(msg.chunk().as_ref(), b"1111");
335
336        buf.extend(b"4\r\ndata\r");
337        let msg = pl.decode(&mut buf).unwrap().unwrap();
338        assert_eq!(msg.chunk().as_ref(), b"data");
339
340        buf.extend(b"\n4");
341        assert!(pl.decode(&mut buf).unwrap().is_none());
342
343        buf.extend(b"\r");
344        assert!(pl.decode(&mut buf).unwrap().is_none());
345        buf.extend(b"\n");
346        assert!(pl.decode(&mut buf).unwrap().is_none());
347
348        buf.extend(b"li");
349        let msg = pl.decode(&mut buf).unwrap().unwrap();
350        assert_eq!(msg.chunk().as_ref(), b"li");
351
352        //trailers
353        //buf.feed_data("test: test\r\n");
354        //not_ready!(reader.parse(&mut buf, &mut readbuf));
355
356        buf.extend(b"ne\r\n0\r\n");
357        let msg = pl.decode(&mut buf).unwrap().unwrap();
358        assert_eq!(msg.chunk().as_ref(), b"ne");
359        assert!(pl.decode(&mut buf).unwrap().is_none());
360
361        buf.extend(b"\r\n");
362        assert!(pl.decode(&mut buf).unwrap().unwrap().eof());
363    }
364
365    #[test]
366    fn chunk_extension_quoted() {
367        let mut buf = BytesMut::from(
368            "GET /test HTTP/1.1\r\n\
369            Host: localhost:8080\r\n\
370            Transfer-Encoding: chunked\r\n\
371            \r\n\
372            2;hello=b;one=\"1 2 3\"\r\n\
373            xx",
374        );
375
376        let mut reader = MessageDecoder::<Request>::default();
377        let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
378        let mut pl = pl.unwrap();
379
380        let chunk = pl.decode(&mut buf).unwrap().unwrap();
381        assert_eq!(chunk, PayloadItem::Chunk(Bytes::from_static(b"xx")));
382    }
383
384    #[test]
385    fn hrs_chunk_extension_invalid() {
386        let mut buf = BytesMut::from(
387            "GET / HTTP/1.1\r\n\
388            Host: localhost:8080\r\n\
389            Transfer-Encoding: chunked\r\n\
390            \r\n\
391            2;x\nx\r\n\
392            4c\r\n\
393            0\r\n",
394        );
395
396        let mut reader = MessageDecoder::<Request>::default();
397        let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
398        let mut pl = pl.unwrap();
399
400        let err = pl.decode(&mut buf).unwrap_err();
401        assert!(err
402            .to_string()
403            .contains("Invalid character in chunk extension"));
404    }
405
406    #[test]
407    fn hrs_chunk_size_overflow() {
408        let mut buf = BytesMut::from(
409            "GET / HTTP/1.1\r\n\
410            Host: example.com\r\n\
411            Transfer-Encoding: chunked\r\n\
412            \r\n\
413            f0000000000000003\r\n\
414            abc\r\n\
415            0\r\n",
416        );
417
418        let mut reader = MessageDecoder::<Request>::default();
419        let (_msg, pl) = reader.decode(&mut buf).unwrap().unwrap();
420        let mut pl = pl.unwrap();
421
422        let err = pl.decode(&mut buf).unwrap_err();
423        assert!(err
424            .to_string()
425            .contains("Invalid chunk size line: Size is too big"));
426    }
427}