nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: hyperium/hyper
Pull request #3698 (2024-07-09 12:57:38)

fix(http1): reject final chunked if missing 0
If a chunked body had valid chunks, but ended without a `0` in the final chunk (so, just `\r\n\r\n`), it would be parsed as a valid end. Now it will be rejected as the final chunk MUST be `0\r\n\r\n`.

This was partially done before (https://github.com/hyperium/hyper/pull/3494), but only if there were no chunks before the final. This fixes both paths.




Patch:
diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs
index 275d6a9b..2e196c36 100644
--- a/src/proto/h1/decode.rs
+++ b/src/proto/h1/decode.rs
@@ -517,7 +517,7 @@ impl ChunkedState {
         rdr: &mut R,
     ) -> Poll<Result<ChunkedState, io::Error>> {
         match byte!(rdr, cx) {
-            b'\n' => Poll::Ready(Ok(ChunkedState::Size)),
+            b'\n' => Poll::Ready(Ok(ChunkedState::Start)),
             _ => Poll::Ready(Err(io::Error::new(
                 io::ErrorKind::InvalidInput,
                 "Invalid chunk body LF",
@@ -902,6 +902,26 @@ mod tests {
         assert_eq!("1234567890abcdef", &result);
     }
 
+    #[tokio::test]
+    async fn test_read_chunked_with_missing_zero_digit() {
+        // After reading a valid chunk, the ending is missing a zero.
+        let mut mock_buf = &b"1\r\nZ\r\n\r\n\r\n"[..];
+        let mut decoder = Decoder::chunked(None, None);
+        let buf = decoder
+            .decode_fut(&mut mock_buf)
+            .await
+            .expect("decode")
+            .into_data()
+            .expect("unknown frame type");
+        assert_eq!("Z", buf);
+
+        let err = decoder
+            .decode_fut(&mut mock_buf)
+            .await
+            .expect_err("decode 2");
+        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
+    }
+
     #[tokio::test]
     async fn test_read_chunked_extensions_over_limit() {
         // construct a chunked body where each individual chunked extension


Test patch:
diff --git a/tests/server.rs b/tests/server.rs
index 82bc8066..5120ad77 100644
--- a/tests/server.rs
+++ b/tests/server.rs
@@ -572,6 +572,29 @@ fn post_with_incomplete_body() {
     req.read(&mut [0; 256]).expect("read");
 }
 
+#[test]
+fn post_with_chunked_missing_final_digit() {
+    let _ = pretty_env_logger::try_init();
+    let server = serve();
+    let mut req = connect(server.addr());
+    req.write_all(
+        b"\
+        POST / HTTP/1.1\r\n\
+        Host: example.domain\r\n\
+        transfer-encoding: chunked\r\n\
+        \r\n\
+        1\r\n\
+        Z\r\n\
+        \r\n\r\n\
+    ",
+    )
+    .expect("write");
+
+    server.body_err();
+
+    req.read(&mut [0; 256]).expect("read");
+}
+
 #[test]
 fn head_response_can_send_content_length() {
     let _ = pretty_env_logger::try_init();
Repository: hyperium/hyper
Pull request #3699 (2024-07-09 13:08:38)

fix(http1): reject final chunked if missing 0
This is a backport of #3698 to 0.14.x.

---

If a chunked body had valid chunks, but ended without a `0` in the final chunk (so, just `\r\n\r\n`), it would be parsed as a valid end. Now it will be rejected as the final chunk MUST be `0\r\n\r\n`.

This was partially done before, but only if there were no chunks before the final. This fixes both paths.




Patch:
diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs
index 6752d583..32068635 100644
--- a/src/proto/h1/decode.rs
+++ b/src/proto/h1/decode.rs
@@ -431,7 +431,7 @@ impl ChunkedState {
         rdr: &mut R,
     ) -> Poll<Result<ChunkedState, io::Error>> {
         match byte!(rdr, cx) {
-            b'\n' => Poll::Ready(Ok(ChunkedState::Size)),
+            b'\n' => Poll::Ready(Ok(ChunkedState::Start)),
             _ => Poll::Ready(Err(io::Error::new(
                 io::ErrorKind::InvalidInput,
                 "Invalid chunk body LF",
@@ -666,6 +666,21 @@ mod tests {
         assert_eq!("1234567890abcdef", &result);
     }
 
+    #[tokio::test]
+    async fn test_read_chunked_with_missing_zero_digit() {
+        // After reading a valid chunk, the ending is missing a zero.
+        let mut mock_buf = &b"1\r\nZ\r\n\r\n\r\n"[..];
+        let mut decoder = Decoder::chunked();
+        let buf = decoder.decode_fut(&mut mock_buf).await.expect("decode");
+        assert_eq!("Z", buf);
+
+        let err = decoder
+            .decode_fut(&mut mock_buf)
+            .await
+            .expect_err("decode 2");
+        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
+    }
+
     #[tokio::test]
     async fn test_read_chunked_extensions_over_limit() {
         // construct a chunked body where each individual chunked extension


Test patch:
diff --git a/tests/server.rs b/tests/server.rs
index 786bb4b4..09f0dffb 100644
--- a/tests/server.rs
+++ b/tests/server.rs
@@ -585,6 +585,29 @@ fn post_with_incomplete_body() {
     req.read(&mut [0; 256]).expect("read");
 }
 
+#[test]
+fn post_with_chunked_missing_final_digit() {
+    let _ = pretty_env_logger::try_init();
+    let server = serve();
+    let mut req = connect(server.addr());
+    req.write_all(
+        b"\
+        POST / HTTP/1.1\r\n\
+        Host: example.domain\r\n\
+        transfer-encoding: chunked\r\n\
+        \r\n\
+        1\r\n\
+        Z\r\n\
+        \r\n\r\n\
+    ",
+    )
+    .expect("write");
+
+    server.body_err();
+
+    req.read(&mut [0; 256]).expect("read");
+}
+
 #[test]
 fn head_response_can_send_content_length() {
     let _ = pretty_env_logger::try_init();
Repository: hyperium/hyper
Pull request #3813 (2024-12-16 15:39:30)

Backport 0.14: fix intermitent panic parsing partial headers
Backports #3812 to 0.14.x.




Patch:
diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs
index ac494b93..88ce9dac 100644
--- a/src/proto/h1/io.rs
+++ b/src/proto/h1/io.rs
@@ -248,7 +248,11 @@ where
                         }
                     }
                     if curr_len > 0 {
+                        trace!("partial headers; {} bytes so far", curr_len);
                         self.partial_len = Some(curr_len);
+                    } else {
+                        // 1xx gobled some bytes
+                        self.partial_len = None;
                     }
                 }
             }


Test patch:
diff --git a/tests/client.rs b/tests/client.rs
index 9d7bf083..a5fc79da 100644
--- a/tests/client.rs
+++ b/tests/client.rs
@@ -2908,6 +2908,63 @@ mod conn {
         assert_eq!(vec, b"bar=foo");
     }
 
+    #[tokio::test]
+    async fn client_100_then_http09() {
+        let _ = ::pretty_env_logger::try_init();
+
+        let server = TcpListener::bind("127.0.0.1:0").unwrap();
+        let addr = server.local_addr().unwrap();
+
+        thread::spawn(move || {
+            let mut sock = server.accept().unwrap().0;
+            sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
+            sock.set_write_timeout(Some(Duration::from_secs(5)))
+                .unwrap();
+            let mut buf = [0; 4096];
+            sock.read(&mut buf).expect("read 1");
+            sock.write_all(
+                b"\
+                HTTP/1.1 100 Continue\r\n\
+                Content-Type: text/plain\r\n\
+                Server: BaseHTTP/0.6 Python/3.12.5\r\n\
+                Date: Mon, 16 Dec 2024 03:08:27 GMT\r\n\
+            ",
+            )
+            .unwrap();
+            // That it's separate writes is important to this test
+            thread::sleep(Duration::from_millis(50));
+            sock.write_all(
+                b"\
+                \r\n\
+            ",
+            )
+            .expect("write 2");
+            thread::sleep(Duration::from_millis(50));
+            sock.write_all(
+                b"\
+                This is a sample text/plain document, without final headers.\
+                \n\n\
+            ",
+            )
+            .expect("write 3");
+        });
+
+        let tcp = tcp_connect(&addr).await.unwrap();
+
+        let (mut client, conn) = conn::Builder::new()
+            .http09_responses(true)
+            .handshake(tcp)
+            .await
+            .unwrap();
+
+        tokio::spawn(async move {
+            let _ = conn.await;
+        });
+
+        let req = Request::builder().uri("/a").body(Body::empty()).unwrap();
+        let _res = client.send_request(req).await.expect("send_request");
+    }
+
     #[tokio::test]
     async fn http2_detect_conn_eof() {
         use futures_util::future;