use std::fs::File; use std::io::{ErrorKind, Read}; use std::path::PathBuf; use std::rc::Rc; use hyper::{Body, Request, Response, StatusCode}; use rhai::{Dynamic, FnPtr, Map}; #[derive(Clone, Debug)] pub struct Params { root: PathBuf, } #[derive(Debug)] pub struct KeyError(&'static str); impl TryFrom for Params { type Error = KeyError; fn try_from(mut value: Map) -> Result { let root = value .remove("root") .and_then(|value| value.into_immutable_string().ok()) .ok_or(KeyError("root"))?; let root: PathBuf = root.parse().unwrap(); let root = root.canonicalize().unwrap(); Ok(Self { root }) } } pub fn handle_request(params: &mut Params, request: Rc>) -> Rc> { let Params { root } = params; eprintln!("handling request {:?}", request); let request_path = request.uri().path(); let request_path = request_path.trim_start_matches("/"); let target = root.join(request_path); let target = if target.is_dir() { target.join("index.html") } else { target }; eprintln!("target is {}", target.display()); let response = if target.ancestors().all(|ancestor| ancestor != root) { // oops! all directory traversal Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::empty()) } else { let file = File::open(target); match file { Ok(mut file) => { let mut result = Vec::new(); file.read_to_end(&mut result).unwrap(); Response::builder().body(Body::from(result)) } Err(err) => { let status_code = match err.kind() { ErrorKind::NotFound => StatusCode::NOT_FOUND, _ => StatusCode::BAD_REQUEST, }; Response::builder() .status(status_code) .body(Body::from(status_code.canonical_reason().unwrap_or(""))) } } }; Rc::new(response.unwrap()) } pub fn serve_static(params: Map) -> FnPtr { let params: Params = params.try_into().unwrap(); let mut result = FnPtr::new("handle_request_serve_static").unwrap(); result.add_curry(Dynamic::from(params)); result }