/// A simple handler for disk based files. Will optionally chop off a prefix. use super::{Handler as MajHandler, Request, Result}; use crate::Response; use async_trait::async_trait; use std::ffi::OsStr; use std::path::PathBuf; pub struct Handler { base_dir: PathBuf, } impl Handler { /// Serves static files from an OS directory with a given prefix chopped off. pub fn new(base_dir: PathBuf) -> Self { Handler { base_dir: base_dir } } } #[async_trait] impl MajHandler for Handler { async fn handle(&self, r: Request) -> Result { let mut path = std::path::PathBuf::from(&self.base_dir); if let Some(segments) = r.url.path_segments() { path.extend(segments); } log::debug!("opening file {:?}", path); match tokio::fs::metadata(&path).await { Ok(stat) => { if stat.is_dir() { if r.url.as_str().ends_with('/') { path.push("index.gmi"); } else { // Send a redirect when the URL for a directory has no trailing slash. return Ok(Response::perm_redirect(format!("{}/", r.url))); } } } Err(why) => { log::error!("file {} not found: {}", path.to_str().unwrap(), why); return Ok(Response::not_found()); } } let mut file = tokio::fs::File::open(&path).await?; let mut buf: Vec = Vec::new(); tokio::io::copy(&mut file, &mut buf).await?; // Send header. if path.extension() == Some(OsStr::new("gmi")) || path.extension() == Some(OsStr::new("gemini")) { return Ok(Response::gemini(buf)); } let mime = mime_guess::from_path(&path).first_or_octet_stream(); Ok(Response::with_body(mime.essence_str().to_string(), buf)) } }