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 crate::{route, seg, split};
use async_trait::async_trait;
use std::collections::HashMap;
use std::io::Cursor;
use std::path::PathBuf;
use std::process::Command;

pub struct Handler {
    base_dir: PathBuf,
}

const APPLICATION_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));

#[async_trait]
impl MajHandler for Handler {
    async fn handle(&self, r: Request) -> Result<Response> {
        route!(r.url.path(), {
            (/"cgi-bin"/[prog_name: String][/rest..]) => self.do_cgi(prog_name, rest.to_string(), r).await;
        });

        Ok(Response::not_found())
    }
}

impl Handler {
    pub fn new(base_dir: PathBuf) -> Self {
        Handler { base_dir: base_dir }
    }

    async fn do_cgi(&self, prog_name: String, rest: String, r: Request) -> Result<Response> {
        let mut path = PathBuf::from(&self.base_dir);
        path.push(&prog_name);

        log::debug!("path: {:?}", path);
        let query = {
            match r.url.query() {
                Some(q) => q.clone(),
                None => "",
            }
        };

        if let Err(why) = std::fs::metadata(&path) {
            log::error!("can't find {:?}: {}", path, why);
            return Ok(Response::not_found());
        }

        let filtered_env: HashMap<String, String> = std::env::vars()
            .filter(|&(ref k, _)| k == "TERM" || k == "TZ" || k == "LANG" || k == "PATH")
            .collect();

        let remote_host = format!("{}", r.addr);

        let output = Command::new(&path)
            .env_clear()
            .envs(filtered_env)
            .env("GATEWAY_INTERFACE", "CGI/1.1")
            .env("SERVER_PROTOCOL", "GEMINI")
            .env("SERVER_SOFTWARE", APPLICATION_NAME)
            .env("GEMINI_URL", format!("{}", r.url))
            .env("SCRIPT_NAME", path)
            .env("PATH_INFO", rest)
            .env("QUERY_STRING", query)
            .env("SERVER_NAME", r.url.host_str().unwrap())
            .env("SERVER_HOSTNAME", r.url.host_str().unwrap())
            .env("SERVER_PORT", format!("{}", r.url.port().unwrap_or(1965)))
            .env("REMOTE_HOST", &remote_host)
            .env("REMOTE_ADDR", remote_host)
            .env("TLS_CIPHER", "Secure")
            .env("TLS_VERSION", "TLSv1.3")
            .output()?;

        let resp = Response::parse(&mut Cursor::new(output.stdout))?;

        Ok(resp)
    }
}