#![cfg(target_os = "linux")]
#![allow(clippy::too_many_arguments)]
mod support;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;
use dope_test::Harness;
use http::StatusCode;
use sark::{Executor, Throughput, body, driver};
#[sark_gen::request]
struct PingReq {}
#[sark_gen::response(raw)]
struct PingReply {
status: StatusCode,
body: Vec<u8>,
}
#[sark_gen::handler]
fn ping(_req: PingReq, _state: &()) -> PingReply {
let mut body = Vec::new();
body.extend_from_slice(b"pong");
PingReply {
status: StatusCode::OK,
body,
}
}
#[sark_gen::request]
struct EchoReq {}
#[sark_gen::response(raw)]
struct EchoReply {
status: StatusCode,
body: Vec<u8>,
}
#[sark_gen::handler]
fn echo(_request: EchoReq, _state: &()) -> EchoReply {
EchoReply {
status: StatusCode::OK,
body: body!("echoed"),
}
}
sark_gen::define_route! {
ClOverflowDispatch: () => {
GET "/ping" => ping,
POST "/echo" => echo,
}
}
fn read_head(sock: &mut TcpStream) -> Vec<u8> {
sock.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
let mut out = Vec::new();
let mut buf = [0u8; 4096];
loop {
match sock.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
out.extend_from_slice(&buf[..n]);
if out.windows(4).any(|w| w == b"\r\n\r\n") {
break;
}
}
Err(e)
if e.kind() == std::io::ErrorKind::WouldBlock
|| e.kind() == std::io::ErrorKind::TimedOut =>
{
break;
}
Err(e) => panic!("read: {e}"),
}
}
out
}
#[test]
fn bad_content_length_does_not_panic_and_server_survives() {
let bind: std::net::SocketAddr = "127.0.0.1:18920".parse().unwrap();
let server = support::http_server(bind, Duration::from_secs(10));
Harness::new(bind).run_with_trigger(
|_ctx, trigger| {
let driver_config =
driver::Config::for_tcp_profile::<Throughput>(support::MAX_CONNECTIONS);
let executor = Executor::new(driver_config)?.with_storage(
dope_net::link::egress::storage::Storage::default(),
);
executor.enter(|mut session| {
let timer =
sark::Timer::new();
server.clone().serve(
&mut session,
ClOverflowDispatch::new(
&(),
&timer,
sark::app::Config {
task_capacity: support::MAX_CONNECTIONS,
},
),
Some(trigger),
)
})
},
|bind| {
let bad_clens: &[&str] = &[
"4294967295",
"99999999999999999999999999999999999999",
"12abc",
"18446744073709551615",
"-5",
" ",
];
for clen in bad_clens {
let mut sock = TcpStream::connect(bind).expect("connect");
let req =
format!("POST /echo HTTP/1.1\r\nHost: x\r\nContent-Length: {clen}\r\n\r\n");
sock.write_all(req.as_bytes()).unwrap();
let _ = sock.write_all(b"hi");
let resp = read_head(&mut sock);
if !resp.is_empty() {
let text = String::from_utf8_lossy(&resp);
assert!(
text.starts_with("HTTP/1.1 4"),
"bad Content-Length {clen:?} produced non-4xx response: {text}"
);
}
}
let mut sock = TcpStream::connect(bind).expect("connect");
sock.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
sock.write_all(b"GET /ping HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
.unwrap();
let mut last = String::new();
sock.read_to_string(&mut last).unwrap();
assert!(last.contains("200 OK"), "server died after bad CL: {last}");
assert!(last.contains("pong"), "server died after bad CL: {last}");
let mut sock = TcpStream::connect(bind).expect("connect");
sock.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
let body = b"hello-body";
let req = format!(
"POST /echo HTTP/1.1\r\nHost: x\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
body.len()
);
sock.write_all(req.as_bytes()).unwrap();
sock.write_all(body).unwrap();
let mut resp = String::new();
sock.read_to_string(&mut resp).unwrap();
assert!(resp.contains("200 "), "well-formed POST failed: {resp}");
assert!(resp.contains("echoed"), "echo handler not reached: {resp}");
},
).expect("harness");
}