diff --git a/src/client.rs b/src/client.rs index a1a9a7816..39c5765bf 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1727,6 +1727,11 @@ impl proto::Peer for Peer { if let Some(status) = pseudo.status { b = b.status(status); + } else { + // Every response must include :status (RFC 9113, section 8.3.2). + // https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.2 + proto_err!(stream: "missing :status; stream={:?}", stream_id); + return Err(Error::library_reset(stream_id, Reason::PROTOCOL_ERROR)); } let mut response = match b.body(()) { diff --git a/tests/h2-tests/tests/client_request.rs b/tests/h2-tests/tests/client_request.rs index 04f8dddaf..7b8ae7849 100644 --- a/tests/h2-tests/tests/client_request.rs +++ b/tests/h2-tests/tests/client_request.rs @@ -820,6 +820,75 @@ async fn sending_request_on_closed_connection() { join(srv, h2).await; } +#[tokio::test] +async fn response_missing_status() { + h2_support::trace_init!(); + let (io, mut srv) = mock::new(); + + let srv = async move { + srv.assert_client_handshake().await; + srv.recv_frame( + frames::headers(1) + .request("GET", "https://example.com/") + .eos(), + ) + .await; + + srv.send_frame(frames::headers(1).eos()).await; + + srv.recv_frame( + frames::headers(3) + .request("GET", "https://example.com/") + .eos(), + ) + .await; + + srv.send_frame(frames::headers(3).response(103)).await; + srv.send_frame(frames::headers(3).field("server", "test")) + .await; + + srv.recv_frame(frames::reset(3).protocol_error()).await; + srv.recv_frame( + frames::headers(5) + .request("GET", "https://example.com/") + .eos(), + ) + .await; + + srv.send_frame(frames::headers(5).response(204).eos()).await; + }; + + let client = async move { + let (mut client, mut conn) = client::handshake(io).await.unwrap(); + + let request = Request::get("https://example.com/").body(()).unwrap(); + let (response, _) = client.send_request(request, true).unwrap(); + let err = conn + .drive(response) + .await + .expect_err("stream 1: empty response without :status"); + assert!(err.is_reset()); + assert_eq!(err.reason(), Some(Reason::PROTOCOL_ERROR)); + + let request = Request::get("https://example.com/").body(()).unwrap(); + let (response, _) = client.send_request(request, true).unwrap(); + let err = conn + .drive(response) + .await + .expect_err("stream 3: final response without :status after 103"); + assert!(err.is_reset()); + assert_eq!(err.reason(), Some(Reason::PROTOCOL_ERROR)); + + let request = Request::get("https://example.com/").body(()).unwrap(); + let (response, _) = client.send_request(request, true).unwrap(); + assert_eq!(conn.drive(response).await.unwrap().status(), 204); + }; + + tokio::time::timeout(std::time::Duration::from_secs(5), join(srv, client)) + .await + .expect("response_missing_status timed out"); +} + #[tokio::test] async fn recv_too_big_headers() { h2_support::trace_init!(); diff --git a/tests/h2-tests/tests/stream_states.rs b/tests/h2-tests/tests/stream_states.rs index 531f39d0a..e302f2328 100644 --- a/tests/h2-tests/tests/stream_states.rs +++ b/tests/h2-tests/tests/stream_states.rs @@ -1482,7 +1482,7 @@ async fn srv_window_update_on_lower_stream_id() { frames::push_promise(7, 2).request("GET", "https://http2.akamai.com/style.css"), ) .await; - srv.send_frame(frames::headers(7).eos()).await; + srv.send_frame(frames::headers(7).response(200).eos()).await; srv.recv_frame(frames::reset(2).cancel()).await; srv.send_frame(frames::window_update(5, 66666)).await; };