fix(http1): ignore chunked trailers (#2357)
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
This commit is contained in:
@@ -55,6 +55,8 @@ enum ChunkedState {
|
|||||||
Body,
|
Body,
|
||||||
BodyCr,
|
BodyCr,
|
||||||
BodyLf,
|
BodyLf,
|
||||||
|
Trailer,
|
||||||
|
TrailerLf,
|
||||||
EndCr,
|
EndCr,
|
||||||
EndLf,
|
EndLf,
|
||||||
End,
|
End,
|
||||||
@@ -196,6 +198,8 @@ impl ChunkedState {
|
|||||||
Body => ChunkedState::read_body(cx, body, size, buf),
|
Body => ChunkedState::read_body(cx, body, size, buf),
|
||||||
BodyCr => ChunkedState::read_body_cr(cx, body),
|
BodyCr => ChunkedState::read_body_cr(cx, body),
|
||||||
BodyLf => ChunkedState::read_body_lf(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),
|
EndCr => ChunkedState::read_end_cr(cx, body),
|
||||||
EndLf => ChunkedState::read_end_lf(cx, body),
|
EndLf => ChunkedState::read_end_lf(cx, body),
|
||||||
End => Poll::Ready(Ok(ChunkedState::End)),
|
End => Poll::Ready(Ok(ChunkedState::End)),
|
||||||
@@ -340,16 +344,36 @@ impl ChunkedState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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::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 trailer end LF",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn read_end_cr<R: MemRead>(
|
fn read_end_cr<R: MemRead>(
|
||||||
cx: &mut task::Context<'_>,
|
cx: &mut task::Context<'_>,
|
||||||
rdr: &mut R,
|
rdr: &mut R,
|
||||||
) -> Poll<Result<ChunkedState, io::Error>> {
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
match byte!(rdr, cx) {
|
match byte!(rdr, cx) {
|
||||||
b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)),
|
b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)),
|
||||||
_ => Poll::Ready(Err(io::Error::new(
|
_ => Poll::Ready(Ok(ChunkedState::Trailer)),
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
"Invalid chunk end CR",
|
|
||||||
))),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn read_end_lf<R: MemRead>(
|
fn read_end_lf<R: MemRead>(
|
||||||
@@ -538,6 +562,15 @@ mod tests {
|
|||||||
assert_eq!("1234567890abcdef", &result);
|
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]
|
#[tokio::test]
|
||||||
async fn test_read_chunked_after_eof() {
|
async fn test_read_chunked_after_eof() {
|
||||||
let mut mock_buf = &b"10\r\n1234567890abcdef\r\n0\r\n\r\n"[..];
|
let mut mock_buf = &b"10\r\n1234567890abcdef\r\n0\r\n\r\n"[..];
|
||||||
|
|||||||
@@ -430,6 +430,69 @@ test! {
|
|||||||
body: None,
|
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! {
|
test! {
|
||||||
name: client_get_req_body_sized,
|
name: client_get_req_body_sized,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user