//! A local HTTP CGI server for integration tests against native git http-backend. use std::{ io::{Read, Write}, net::TcpListener, process::{Command, Stdio}, }; fn main() -> Result<(), Box> { let args: Vec<_> = std::env::args().collect(); let listener = TcpListener::bind("127.0.0.1:0")?; std::fs::write(&args[2], listener.local_addr()?.port().to_string())?; for stream in listener.incoming() { let mut stream = stream?; if let Err(error) = serve(&mut stream, &args[1]) { eprintln!("request failed: {error}"); } } Ok(()) } fn serve(io: &mut (impl Read + Write), root: &str) -> Result<(), Box> { let mut header = Vec::new(); while !header.ends_with(b"\r\n\r\n") { let mut byte = [0]; io.read_exact(&mut byte)?; header.push(byte[0]); } let text = std::str::from_utf8(&header)?; let mut lines = text.split("\r\n"); let request: Vec<_> = lines .next() .ok_or("no request")? .split_whitespace() .collect(); let mut length = 0; let mut content_type = ""; for line in lines { if let Some((key, value)) = line.split_once(':') { if key.eq_ignore_ascii_case("content-length") { length = value.trim().parse()?; } if key.eq_ignore_ascii_case("content-type") { content_type = value.trim(); } } } let mut body = vec![0; length]; io.read_exact(&mut body)?; let (path, query) = request[1].split_once('?').unwrap_or((request[1], "")); let mut child = Command::new("git") .arg("http-backend") .env("GIT_PROJECT_ROOT", root) .env("GIT_HTTP_EXPORT_ALL", "1") .env("PATH_INFO", path) .env("QUERY_STRING", query) .env("REQUEST_METHOD", request[0]) .env("CONTENT_TYPE", content_type) .env("CONTENT_LENGTH", length.to_string()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; child.stdin.take().ok_or("no CGI stdin")?.write_all(&body)?; let response = child.wait_with_output()?; let split = response .stdout .windows(4) .position(|w| w == b"\r\n\r\n") .ok_or("invalid CGI response")?; let headers = std::str::from_utf8(&response.stdout[..split])?; let status = headers .lines() .find_map(|l| l.strip_prefix("Status: ")) .unwrap_or("200 OK"); write!( io, "HTTP/1.1 {status}\r\nConnection: close\r\nContent-Length: {}\r\n", response.stdout.len() - split - 4 )?; for line in headers.split("\r\n") { if !line.starts_with("Status:") { write!(io, "{line}\r\n")?; } } io.write_all(b"\r\n")?; io.write_all(&response.stdout[split + 4..])?; io.flush()?; Ok(()) }