use anyhow::{Result, anyhow}; use clap::Parser; use log::{error, warn}; use std::{ io::{BufRead, BufReader, BufWriter, Write}, marker::PhantomData, net::{IpAddr, SocketAddr, TcpListener, TcpStream}, path::PathBuf, sync::Arc, thread::spawn, }; use weareshared::{packets::Resource, store::ResourceStore}; #[derive(Parser, Debug)] struct Args { #[arg(short, long, default_value = "::")] bind_addr: IpAddr, #[arg(short, long, default_value = "28556")] port: u16, pack: PathBuf, } fn main() -> Result<()> { env_logger::init_from_env("LOG"); let args = Args::parse(); let listener = TcpListener::bind(SocketAddr::new(args.bind_addr, args.port))?; let store = Arc::new(ResourceStore::new_respack_file(&args.pack)?); loop { let (conn, _addr) = listener.accept()?; let store = store.clone(); spawn(move || { if let Err(e) = handle_conn(conn, store) { warn!("{e}") } }); } } fn handle_conn(conn: TcpStream, store: Arc) -> Result<()> { let mut conn_read = BufReader::new(conn.try_clone()?); let mut conn_write = BufWriter::new(conn); let mut buf = String::new(); while !buf.ends_with("\r\n\r\n") { conn_read.read_line(&mut buf)?; } let mut lines = buf.lines(); let mut status = lines .next() .ok_or(anyhow!("HTTP status line missing"))? .split(" "); let _method = status.next().ok_or(anyhow!("HTTP method missing"))?; let path = status.next().ok_or(anyhow!("HTTP path missing"))?; let _httpver = status.next().ok_or(anyhow!("HTTP version missing"))?; let path = path .strip_prefix("/") .ok_or(anyhow!("path does not start on /"))?; let (code, ty, data) = handle_request(path, &store); write!(conn_write, "HTTP/1.0 {code} OK\r\n")?; write!(conn_write, "content-type: {ty}\r\n")?; write!(conn_write, "server: write! macros\r\n")?; write!(conn_write, "content-length: {}\r\n", data.len())?; write!(conn_write, "\r\n")?; conn_write.write_all(&data)?; conn_write.flush()?; Ok(()) } fn handle_request(path: &str, store: &ResourceStore) -> (u16, &'static str, Vec) { if path == "entry" { if let ResourceStore::Respack(pack) = store { let pack = pack.lock().unwrap(); if let Some(entry) = pack.entry() { (200, "application/x.weareresource.id", entry.0.to_vec()) } else { (404, "text/plain", b"No entry found".to_vec()) } } else { unreachable!() } } else if path.len() == 64 { let mut hash = [0; 32]; if let Err(_) = hex::decode_to_slice(path, &mut hash) { return (400, "text/plain", b"Invalid hash format".to_vec()); } match store.get_raw(Resource(hash, PhantomData)) { Ok(Some(res)) => (200, "application/x.weareresource", res), Ok(None) => (404, "text/plain", b"Not found".to_vec()), Err(e) => { error!("{e}"); (500, "text/plain", b"Internal server error".to_vec()) } } } else { (400, "text/plain", b"Bad path".to_vec()) } }