Skip to content

Commit

Permalink
fix(http1): ignore chunked trailers (hyperium#2363)
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 hyperium#2171

Co-authored-by: Alex Rebert <alex@forallsecure.com>
  • Loading branch information
MOZGIII and alpire committed Dec 16, 2020
1 parent 42560c7 commit a00cc20
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
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 @@ -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"[..];
Expand Down
63 changes: 63 additions & 0 deletions tests/client.rs
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 a00cc20

Please sign in to comment.