use std::collections::HashMap;
use std::io::Write;
use std::net::TcpStream;
use super::ServiceConfig;
pub(super) fn write_response(
stream: &mut TcpStream,
status: &str,
content_type: &str,
body: &str,
) -> std::io::Result<()> {
let response = format!(
concat!(
"HTTP/1.1 {}\r\n",
"Content-Type: {}\r\n",
"Content-Length: {}\r\n",
"Connection: close\r\n",
"\r\n",
"{}"
),
status,
content_type,
body.len(),
body
);
stream.write_all(response.as_bytes())?;
stream.flush()
}
pub(super) fn split_target(target: &str) -> (&str, &str) {
match target.split_once('?') {
Some((path, query)) => (path, query),
None => (target, ""),
}
}
pub(super) fn parse_query_params(query: &str) -> HashMap<String, String> {
if query.is_empty() {
return HashMap::new();
}
query
.split('&')
.filter(|part| !part.is_empty())
.filter_map(|part| part.split_once('='))
.map(|(key, value)| (key.to_string(), url_decode(value)))
.collect()
}
fn url_decode(value: &str) -> String {
let mut out = String::with_capacity(value.len());
let bytes = value.as_bytes();
let mut idx = 0usize;
while idx < bytes.len() {
match bytes[idx] {
b'+' => {
out.push(' ');
idx += 1;
}
b'%' if idx + 2 < bytes.len() => {
let hex = &value[idx + 1..idx + 3];
if let Ok(decoded) = u8::from_str_radix(hex, 16) {
out.push(decoded as char);
idx += 3;
} else {
out.push('%');
idx += 1;
}
}
other => {
out.push(other as char);
idx += 1;
}
}
}
out
}
pub(super) fn admin_authorized(config: &ServiceConfig, headers: &HashMap<String, String>) -> bool {
let Some(expected) = &config.admin_token else {
return true;
};
headers
.get("authorization")
.and_then(|value| value.strip_prefix("Bearer "))
.map(|token| token == expected)
.unwrap_or(false)
}
pub(super) fn unix_now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("clock after unix epoch")
.as_secs()
}