From 84b78b6c877ff9aaa28d1e348a5deb63a9282503 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Mon, 6 Dec 2021 14:14:41 -0800 Subject: [PATCH] fix(http2): received `Body::size_hint()` now return 0 if implicitly empty (#2715) An HTTP/2 stream may include a set of headers, and a flag signalling END-STREAM, even if a `content-length` isn't included. hyper wouldn't notice, and so the `Body` would report a size-hint of `0..MAX`. hyper now notices that the stream is ended, and couldn't possibly include any bytes for the body, and thus will give a size-hint of `0` exactly. --- src/body/body.rs | 7 ++++++- src/body/length.rs | 10 ++++++++++ src/proto/h2/server.rs | 9 +++++---- tests/server.rs | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/body/body.rs b/src/body/body.rs index a1ac6ad3b6..9dc1a034f9 100644 --- a/src/body/body.rs +++ b/src/body/body.rs @@ -204,9 +204,14 @@ impl Body { #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] pub(crate) fn h2( recv: h2::RecvStream, - content_length: DecodedLength, + mut content_length: DecodedLength, ping: ping::Recorder, ) -> Self { + // If the stream is already EOS, then the "unknown length" is clearly + // actually ZERO. + if !content_length.is_exact() && recv.is_end_stream() { + content_length = DecodedLength::ZERO; + } let body = Body::new(Kind::H2 { ping, content_length, diff --git a/src/body/length.rs b/src/body/length.rs index 6e6daa6b09..e2bbee8039 100644 --- a/src/body/length.rs +++ b/src/body/length.rs @@ -68,6 +68,16 @@ impl DecodedLength { } } } + + /// Returns whether this represents an exact length. + /// + /// This includes 0, which of course is an exact known length. + /// + /// It would return false if "chunked" or otherwise size-unknown. + #[cfg(feature = "http2")] + pub(crate) fn is_exact(&self) -> bool { + self.0 <= MAX_LEN + } } impl fmt::Debug for DecodedLength { diff --git a/src/proto/h2/server.rs b/src/proto/h2/server.rs index ad06174646..c9c1380d54 100644 --- a/src/proto/h2/server.rs +++ b/src/proto/h2/server.rs @@ -484,12 +484,13 @@ where } } - // automatically set Content-Length from body... - if let Some(len) = body.size_hint().exact() { - headers::set_content_length_if_missing(res.headers_mut(), len); - } if !body.is_end_stream() { + // automatically set Content-Length from body... + if let Some(len) = body.size_hint().exact() { + headers::set_content_length_if_missing(res.headers_mut(), len); + } + let body_tx = reply!(me, res, false); H2StreamState::Body { pipe: PipeToSendStream::new(body, body_tx), diff --git a/tests/server.rs b/tests/server.rs index 8a974fdd7c..82491ec408 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -361,6 +361,26 @@ mod response_body_lengths { assert_eq!(res.headers().get("content-length").unwrap(), "10"); assert_eq!(res.body().size_hint().exact(), Some(10)); } + + #[tokio::test] + async fn http2_implicit_empty_size_hint() { + use http_body::Body; + + let server = serve(); + let addr_str = format!("http://{}", server.addr()); + server.reply(); + + let client = Client::builder() + .http2_only(true) + .build_http::(); + let uri = addr_str + .parse::() + .expect("server addr should parse"); + + let res = client.get(uri).await.unwrap(); + assert_eq!(res.headers().get("content-length"), None); + assert_eq!(res.body().size_hint().exact(), Some(0)); + } } #[test]