From 586cc7c70d9080cbe00eee718ea84f3cf4207556 Mon Sep 17 00:00:00 2001 From: Alexandre Rebert Date: Sun, 13 Dec 2020 00:41:37 -0500 Subject: [PATCH] fix(h1): ignore chunked trailers Previously, hyper returned an "Invalid chunk end CR" error on chunked responses with trailers, as described in RFC 7230 Section 4.1.2. This commit adds code to ignore the trailers. Closes #2171 --- src/proto/h1/decode.rs | 39 ++++++++++++++++++++++++-- tests/client.rs | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs index 15c934d1f0..ddca5f9d60 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, @@ -538,6 +562,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 92ba534f12..d5092e3584 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,