Skip to content

Commit ddca7fe

Browse files
committed
fix(http1): fix intermitent panic parsing partial headers (#3812)
Closes #3811
1 parent a24f0c0 commit ddca7fe

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

src/proto/h1/io.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,11 @@ where
248248
}
249249
}
250250
if curr_len > 0 {
251+
trace!("partial headers; {} bytes so far", curr_len);
251252
self.partial_len = Some(curr_len);
253+
} else {
254+
// 1xx gobled some bytes
255+
self.partial_len = None;
252256
}
253257
}
254258
}

tests/client.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2908,6 +2908,146 @@ mod conn {
29082908
assert_eq!(vec, b"bar=foo");
29092909
}
29102910

2911+
#[tokio::test]
2912+
async fn client_100_then_http09() {
2913+
let (server, addr) = setup_std_test_server();
2914+
2915+
thread::spawn(move || {
2916+
let mut sock = server.accept().unwrap().0;
2917+
sock.set_read_timeout(Some(Duration::from_secs(5))).unwrap();
2918+
sock.set_write_timeout(Some(Duration::from_secs(5)))
2919+
.unwrap();
2920+
let mut buf = [0; 4096];
2921+
sock.read(&mut buf).expect("read 1");
2922+
sock.write_all(
2923+
b"\
2924+
HTTP/1.1 100 Continue\r\n\
2925+
Content-Type: text/plain\r\n\
2926+
Server: BaseHTTP/0.6 Python/3.12.5\r\n\
2927+
Date: Mon, 16 Dec 2024 03:08:27 GMT\r\n\
2928+
",
2929+
)
2930+
.unwrap();
2931+
// That it's separate writes is important to this test
2932+
thread::sleep(Duration::from_millis(50));
2933+
sock.write_all(
2934+
b"\
2935+
\r\n\
2936+
",
2937+
)
2938+
.expect("write 2");
2939+
thread::sleep(Duration::from_millis(50));
2940+
sock.write_all(
2941+
b"\
2942+
This is a sample text/plain document, without final headers.\
2943+
\n\n\
2944+
",
2945+
)
2946+
.expect("write 3");
2947+
});
2948+
2949+
let tcp = tcp_connect(&addr).await.unwrap();
2950+
2951+
let (mut client, conn) = conn::http1::Builder::new()
2952+
.http09_responses(true)
2953+
.handshake(tcp)
2954+
.await
2955+
.unwrap();
2956+
2957+
tokio::spawn(async move {
2958+
let _ = conn.await;
2959+
});
2960+
2961+
let req = Request::builder()
2962+
.uri("/a")
2963+
.body(Empty::<Bytes>::new())
2964+
.unwrap();
2965+
let _res = client.send_request(req).await.expect("send_request");
2966+
}
2967+
2968+
#[tokio::test]
2969+
async fn test_try_send_request() {
2970+
use std::future::Future;
2971+
let (done_tx, done_rx) = tokio::sync::oneshot::channel::<()>();
2972+
let (io_srv, io_cli) = tokio_test::io::Builder::new()
2973+
.write(b"GET / HTTP/1.1\r\n\r\n")
2974+
.read(b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n")
2975+
.build_with_handle();
2976+
2977+
tokio::spawn(async move {
2978+
let _io = io_cli;
2979+
let _ = done_rx.await;
2980+
});
2981+
2982+
// make polling fair by putting both in spawns
2983+
tokio::spawn(async move {
2984+
let io = TokioIo::new(io_srv);
2985+
let (mut client, mut conn) = conn::http1::Builder::new()
2986+
.handshake::<_, Empty<Bytes>>(io)
2987+
.await
2988+
.expect("http handshake");
2989+
2990+
// get the conn ready
2991+
assert!(
2992+
future::poll_fn(|cx| Poll::Ready(Pin::new(&mut conn).poll(cx)))
2993+
.await
2994+
.is_pending()
2995+
);
2996+
assert!(client.is_ready());
2997+
2998+
// use the connection once
2999+
let mut fut1 = std::pin::pin!(client.send_request(http::Request::new(Empty::new())));
3000+
let _res1 = future::poll_fn(|cx| loop {
3001+
if let Poll::Ready(res) = fut1.as_mut().poll(cx) {
3002+
return Poll::Ready(res);
3003+
}
3004+
return match Pin::new(&mut conn).poll(cx) {
3005+
Poll::Ready(_) => panic!("ruh roh"),
3006+
Poll::Pending => Poll::Pending,
3007+
};
3008+
})
3009+
.await
3010+
.expect("resp 1");
3011+
3012+
assert!(client.is_ready());
3013+
3014+
// simulate the server dropping the conn
3015+
let _ = done_tx.send(());
3016+
// let the server task die
3017+
tokio::task::yield_now().await;
3018+
3019+
let mut fut2 =
3020+
std::pin::pin!(client.try_send_request(http::Request::new(Empty::new())));
3021+
let poll1 = future::poll_fn(|cx| Poll::Ready(fut2.as_mut().poll(cx))).await;
3022+
assert!(poll1.is_pending(), "not already known to error");
3023+
3024+
let mut conn_opt = Some(conn);
3025+
// wasn't a known error, req is in queue, and now the next poll, the
3026+
// conn will be noticed as errored
3027+
let mut err = future::poll_fn(|cx| {
3028+
loop {
3029+
if let Poll::Ready(res) = fut2.as_mut().poll(cx) {
3030+
return Poll::Ready(res);
3031+
}
3032+
if let Some(ref mut conn) = conn_opt {
3033+
match Pin::new(conn).poll(cx) {
3034+
Poll::Ready(_) => {
3035+
conn_opt = None;
3036+
} // ok
3037+
Poll::Pending => return Poll::Pending,
3038+
};
3039+
}
3040+
}
3041+
})
3042+
.await
3043+
.expect_err("resp 2");
3044+
3045+
assert!(err.take_message().is_some(), "request was returned");
3046+
})
3047+
.await
3048+
.unwrap();
3049+
}
3050+
29113051
#[tokio::test]
29123052
async fn http2_detect_conn_eof() {
29133053
use futures_util::future;

0 commit comments

Comments
 (0)