diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs index beaf9aff7a..7612880b42 100644 --- a/src/proto/h1/decode.rs +++ b/src/proto/h1/decode.rs @@ -55,6 +55,8 @@ enum ChunkedState { Body, BodyCr, BodyLf, + Trailer, + TrailerLf, EndCr, EndLf, End, @@ -196,6 +198,8 @@ impl ChunkedState { Body => ChunkedState::read_body(cx, body, size, buf), BodyCr => ChunkedState::read_body_cr(cx, body), BodyLf => ChunkedState::read_body_lf(cx, body), + Trailer => ChunkedState::read_trailer(cx, body), + TrailerLf => ChunkedState::read_trailer_lf(cx, body), EndCr => ChunkedState::read_end_cr(cx, body), EndLf => ChunkedState::read_end_lf(cx, body), End => Poll::Ready(Ok(ChunkedState::End)), @@ -340,18 +344,38 @@ impl ChunkedState { } } - fn read_end_cr( + fn read_trailer( cx: &mut task::Context<'_>, rdr: &mut R, ) -> Poll> { + trace!("read_trailer"); match byte!(rdr, cx) { - b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)), + b'\r' => Poll::Ready(Ok(ChunkedState::TrailerLf)), + _ => Poll::Ready(Ok(ChunkedState::Trailer)), + } + } + fn read_trailer_lf( + cx: &mut task::Context<'_>, + rdr: &mut R, + ) -> Poll> { + match byte!(rdr, cx) { + b'\n' => Poll::Ready(Ok(ChunkedState::EndCr)), _ => Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, - "Invalid chunk end CR", + "Invalid trailer end LF", ))), } } + + fn read_end_cr( + cx: &mut task::Context<'_>, + rdr: &mut R, + ) -> Poll> { + match byte!(rdr, cx) { + b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)), + _ => Poll::Ready(Ok(ChunkedState::Trailer)), + } + } fn read_end_lf( cx: &mut task::Context<'_>, rdr: &mut R, @@ -537,6 +561,15 @@ mod tests { assert_eq!("1234567890abcdef", &result); } + #[tokio::test] + async fn test_read_chunked_trailer_with_missing_lf() { + let mut mock_buf = &b"10\r\n1234567890abcdef\r\n0\r\nbad\r\r\n"[..]; + let mut decoder = Decoder::chunked(); + decoder.decode_fut(&mut mock_buf).await.expect("decode"); + let e = decoder.decode_fut(&mut mock_buf).await.unwrap_err(); + assert_eq!(e.kind(), io::ErrorKind::InvalidInput); + } + #[tokio::test] async fn test_read_chunked_after_eof() { let mut mock_buf = &b"10\r\n1234567890abcdef\r\n0\r\n\r\n"[..]; diff --git a/tests/client.rs b/tests/client.rs index 576423768f..8654d1a382 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -430,6 +430,69 @@ test! { body: None, } +test! { + name: client_get_req_body_chunked_with_trailer, + + server: + expected: "\ + GET / HTTP/1.1\r\n\ + host: {addr}\r\n\ + \r\n\ + ", + reply: "\ + HTTP/1.1 200 OK\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 5\r\n\ + hello\r\n\ + 0\r\n\ + Trailer: value\r\n\ + \r\n\ + ", + + client: + request: { + method: GET, + url: "http://{addr}/", + }, + response: + status: OK, + headers: {}, + body: &b"hello"[..], +} + +test! { + name: client_get_req_body_chunked_with_multiple_trailers, + + server: + expected: "\ + GET / HTTP/1.1\r\n\ + host: {addr}\r\n\ + \r\n\ + ", + reply: "\ + HTTP/1.1 200 OK\r\n\ + Transfer-Encoding: chunked\r\n\ + \r\n\ + 5\r\n\ + hello\r\n\ + 0\r\n\ + Trailer: value\r\n\ + another-trainer: another-value\r\n\ + \r\n\ + ", + + client: + request: { + method: GET, + url: "http://{addr}/", + }, + response: + status: OK, + headers: {}, + body: &b"hello"[..], +} + test! { name: client_get_req_body_sized,