maj 0.6.0

A gemini client and server for Rust
Documentation
/// 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<Response> {
        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 async_std::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 = async_std::fs::File::open(&path).await?;
        let mut buf: Vec<u8> = Vec::new();
        async_std::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))
    }
}