Skip to content

Commit

Permalink
fix(http1): fix intermitent panic parsing partial headers (#3812)
Browse files Browse the repository at this point in the history
Closes #3811
  • Loading branch information
seanmonstar committed Dec 16, 2024
1 parent a24f0c0 commit ddca7fe
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/proto/h1/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down
140 changes: 140 additions & 0 deletions tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2908,6 +2908,146 @@ mod conn {
assert_eq!(vec, b"bar=foo");
}

#[tokio::test]
async fn client_100_then_http09() {
let (server, addr) = setup_std_test_server();

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::http1::Builder::new()
.http09_responses(true)
.handshake(tcp)
.await
.unwrap();

tokio::spawn(async move {
let _ = conn.await;
});

let req = Request::builder()
.uri("/a")
.body(Empty::<Bytes>::new())
.unwrap();
let _res = client.send_request(req).await.expect("send_request");
}

#[tokio::test]
async fn test_try_send_request() {
use std::future::Future;
let (done_tx, done_rx) = tokio::sync::oneshot::channel::<()>();
let (io_srv, io_cli) = tokio_test::io::Builder::new()
.write(b"GET / HTTP/1.1\r\n\r\n")
.read(b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n")
.build_with_handle();

tokio::spawn(async move {
let _io = io_cli;
let _ = done_rx.await;
});

// make polling fair by putting both in spawns
tokio::spawn(async move {
let io = TokioIo::new(io_srv);
let (mut client, mut conn) = conn::http1::Builder::new()
.handshake::<_, Empty<Bytes>>(io)
.await
.expect("http handshake");

// get the conn ready
assert!(
future::poll_fn(|cx| Poll::Ready(Pin::new(&mut conn).poll(cx)))
.await
.is_pending()
);
assert!(client.is_ready());

// use the connection once
let mut fut1 = std::pin::pin!(client.send_request(http::Request::new(Empty::new())));
let _res1 = future::poll_fn(|cx| loop {
if let Poll::Ready(res) = fut1.as_mut().poll(cx) {
return Poll::Ready(res);
}
return match Pin::new(&mut conn).poll(cx) {
Poll::Ready(_) => panic!("ruh roh"),
Poll::Pending => Poll::Pending,
};
})
.await
.expect("resp 1");

assert!(client.is_ready());

// simulate the server dropping the conn
let _ = done_tx.send(());
// let the server task die
tokio::task::yield_now().await;

let mut fut2 =
std::pin::pin!(client.try_send_request(http::Request::new(Empty::new())));
let poll1 = future::poll_fn(|cx| Poll::Ready(fut2.as_mut().poll(cx))).await;
assert!(poll1.is_pending(), "not already known to error");

let mut conn_opt = Some(conn);
// wasn't a known error, req is in queue, and now the next poll, the
// conn will be noticed as errored
let mut err = future::poll_fn(|cx| {
loop {
if let Poll::Ready(res) = fut2.as_mut().poll(cx) {
return Poll::Ready(res);
}
if let Some(ref mut conn) = conn_opt {
match Pin::new(conn).poll(cx) {
Poll::Ready(_) => {
conn_opt = None;
} // ok
Poll::Pending => return Poll::Pending,
};
}
}
})
.await
.expect_err("resp 2");

assert!(err.take_message().is_some(), "request was returned");
})
.await
.unwrap();
}

#[tokio::test]
async fn http2_detect_conn_eof() {
use futures_util::future;
Expand Down

0 comments on commit ddca7fe

Please sign in to comment.