Skip to content

Commit

Permalink
fix(http1): ignore chunked trailers (#2357)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alpire authored Dec 15, 2020
1 parent ede3a6b commit 1dd761c
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 3 deletions.
39 changes: 36 additions & 3 deletions src/proto/h1/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ enum ChunkedState {
Body,
BodyCr,
BodyLf,
Trailer,
TrailerLf,
EndCr,
EndLf,
End,
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -340,18 +344,38 @@ impl ChunkedState {
}
}

fn read_end_cr<R: MemRead>(
fn read_trailer<R: MemRead>(
cx: &mut task::Context<'_>,
rdr: &mut R,
) -> Poll<Result<ChunkedState, io::Error>> {
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<R: MemRead>(
cx: &mut task::Context<'_>,
rdr: &mut R,
) -> Poll<Result<ChunkedState, io::Error>> {
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<R: MemRead>(
cx: &mut task::Context<'_>,
rdr: &mut R,
) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr, cx) {
b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)),
_ => Poll::Ready(Ok(ChunkedState::Trailer)),
}
}
fn read_end_lf<R: MemRead>(
cx: &mut task::Context<'_>,
rdr: &mut R,
Expand Down Expand Up @@ -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"[..];
Expand Down
63 changes: 63 additions & 0 deletions tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down

0 comments on commit 1dd761c

Please sign in to comment.