use std::net::SocketAddr;
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use crate::http::{self, Header, HttpError};
pub(super) async fn respond(
stream: &mut TcpStream,
status: u16,
content_type: &str,
content_length: u64,
extra_headers: Vec<Header<'_>>,
) -> Result<(), HttpError> {
let mut headers = extra_headers;
headers.push(Header {
name: "X-Content-Type-Options",
value: "nosniff",
});
http::write_head(stream, status, content_type, content_length, &headers).await
}
async fn send(
stream: &mut TcpStream,
status: u16,
content_type: &str,
body: &[u8],
headers: Vec<Header<'_>>,
) {
if respond(stream, status, content_type, body.len() as u64, headers)
.await
.is_ok()
{
let _ = stream.write_all(body).await;
}
}
pub(super) async fn reply(
stream: &mut TcpStream,
status: u16,
content_type: &str,
body: &[u8],
headers: Vec<Header<'_>>,
) -> (u16, u64) {
send(stream, status, content_type, body, headers).await;
(status, body.len() as u64)
}
pub(super) fn log_access(
peer: Option<SocketAddr>,
method: &str,
path: &str,
status: u16,
bytes: u64,
) {
let peer = peer.map_or_else(|| "-".to_string(), |addr| addr.to_string());
let path = escape_for_log(path);
println!("{peer} \"{method} {path}\" {status} {bytes}");
}
fn escape_for_log(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for byte in s.bytes() {
if (0x20..=0x7e).contains(&byte) {
out.push(char::from(byte));
} else {
out.push_str(&format!("\\x{byte:02X}"));
}
}
out
}
#[cfg(test)]
mod tests {
use super::escape_for_log;
#[test]
fn a_decoded_c1_control_byte_is_escaped_for_the_log() {
assert_eq!(escape_for_log("/\u{9b}"), "/\\xC2\\x9B");
}
}