diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index fddd1c2c3..119c2b290 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -43,6 +43,9 @@ - Add `body::to_bytes_limited()` function. - Add `body::BodyLimitExceeded` error type. +### Fixed +- Fix truncated body ending without error when connection closed abnormally. [#3067] + ### Changed - Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency. diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index a24a6bb07..2f1d01ef8 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1118,6 +1118,7 @@ where let inner = inner.as_mut().project(); inner.flags.insert(Flags::READ_DISCONNECT); if let Some(mut payload) = inner.payload.take() { + payload.set_error(io::Error::from(io::ErrorKind::UnexpectedEof).into()); payload.feed_eof(); } }; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 4ba64a53c..87e52c15c 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -439,6 +439,60 @@ async fn content_length() { srv.stop().await; } +#[actix_rt::test] +async fn content_length_truncated() { + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + + let mut srv = test_server(|| { + HttpService::build() + .h1(|mut req: Request| async move { + let expected_length: usize = req.uri().path()[1..].parse().unwrap(); + let mut payload = req.take_payload(); + + let mut length = 0; + let mut seen_error = false; + while let Some(chunk) = payload.next().await { + match chunk { + Ok(b) => length += b.len(), + Err(_) => { + seen_error = true; + break; + } + } + } + if seen_error { + return Result::<_, Infallible>::Ok(Response::bad_request()); + } + + assert_eq!(length, expected_length, "length must match when no error"); + Result::<_, Infallible>::Ok(Response::ok()) + }) + .tcp() + }) + .await; + + let addr = srv.addr(); + let mut buf = [0; 12]; + + let mut conn = TcpStream::connect(&addr).await.unwrap(); + conn.write_all(b"POST /10000 HTTP/1.1\r\nContent-Length: 10000\r\n\r\ndata_truncated") + .await + .unwrap(); + conn.shutdown().await.unwrap(); + conn.read_exact(&mut buf).await.unwrap(); + assert_eq!(&buf, b"HTTP/1.1 400"); + + let mut conn = TcpStream::connect(&addr).await.unwrap(); + conn.write_all(b"POST /4 HTTP/1.1\r\nContent-Length: 4\r\n\r\ndata") + .await + .unwrap(); + conn.shutdown().await.unwrap(); + conn.read_exact(&mut buf).await.unwrap(); + assert_eq!(&buf, b"HTTP/1.1 200"); + + srv.stop().await; +} + #[actix_rt::test] async fn h1_headers() { let data = STR.repeat(10);