use std::{
future::Future,
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
time::Duration,
};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub async fn assert_http_redirect_rejected<F, Fut>(send: F)
where
F: FnOnce(String) -> Fut,
Fut: Future<Output = u16>,
{
let origin = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = origin.local_addr().unwrap();
let destination = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let target = destination.local_addr().unwrap();
let destination_requests = Arc::new(AtomicUsize::new(0));
let requests = destination_requests.clone();
let destination_task = tokio::spawn(async move {
let (mut stream, _) = destination.accept().await.unwrap();
read_request_headers(&mut stream).await;
requests.fetch_add(1, Ordering::SeqCst);
stream
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
.await
.unwrap();
});
let origin_task = tokio::spawn(async move {
let (mut stream, _) = origin.accept().await.unwrap();
read_request_headers(&mut stream).await;
stream.write_all(format!("HTTP/1.1 307 Temporary Redirect\r\nContent-Length: 0\r\nConnection: close\r\nLocation: http://{target}/destination\r\n\r\n").as_bytes()).await.unwrap();
});
let result = tokio::time::timeout(
Duration::from_secs(5),
send(format!("http://{addr}/origin")),
)
.await;
origin_task.abort();
destination_task.abort();
assert_eq!(result.unwrap(), 307);
assert_eq!(destination_requests.load(Ordering::SeqCst), 0);
}
async fn read_request_headers(stream: &mut tokio::net::TcpStream) {
let mut headers = Vec::new();
while !headers.ends_with(b"\r\n\r\n") {
let mut byte = [0];
stream.read_exact(&mut byte).await.unwrap();
headers.push(byte[0]);
}
}