From 1da65dcfeb5f05c7b21dbe62b63185d79f7056df Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Sat, 8 Aug 2020 11:23:36 -0400 Subject: [PATCH] add CGI support --- pilno/karnycukta/src/zbasu/mod.rs | 2 +- site/cgi-bin/env.sh | 6 +++ site/src/main.rs | 5 +++ site/src/server.rs | 2 + src/client.rs | 5 +-- src/server/cgi.rs | 72 +++++++++++++++++++++++++++++++ src/server/files.rs | 4 +- src/server/mod.rs | 14 +++++- 8 files changed, 100 insertions(+), 10 deletions(-) create mode 100755 site/cgi-bin/env.sh create mode 100644 src/server/cgi.rs diff --git a/pilno/karnycukta/src/zbasu/mod.rs b/pilno/karnycukta/src/zbasu/mod.rs index f623507..e2f2679 100644 --- a/pilno/karnycukta/src/zbasu/mod.rs +++ b/pilno/karnycukta/src/zbasu/mod.rs @@ -1,11 +1,11 @@ use anyhow::{anyhow, Result}; use atom_syndication as atom; +use chrono::{prelude::*, Duration}; use maj::gemini::Node; use rustls::ClientConfig; use std::io::{self, BufReader, Cursor, Write}; use std::ops::Sub; use std::str; -use chrono::{Duration, prelude::*}; mod tls; diff --git a/site/cgi-bin/env.sh b/site/cgi-bin/env.sh new file mode 100755 index 0000000..0d8848d --- /dev/null +++ b/site/cgi-bin/env.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +echo "20 text/plain" +echo "The following is the CGI environment of this program:" +echo +env diff --git a/site/src/main.rs b/site/src/main.rs index 7209b2c..229d07f 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -37,6 +37,10 @@ struct Options { #[structopt(short = "s", long, env = "STATIC_PATH", default_value = "./static")] static_path: PathBuf, + /// CGI path + #[structopt(short = "C", long, env = "CGI_PATH", default_value = "./cgi-bin")] + cgi_path: PathBuf, + /// server hostname #[structopt( long = "hostname", @@ -83,6 +87,7 @@ fn main() -> Result<(), maj::server::Error> { let h = Arc::new(server::Handler { hostname: opts.hostname, files: maj::server::files::Handler::new(opts.static_path), + cgi: maj::server::cgi::Handler::new(opts.cgi_path), }); { diff --git a/site/src/server.rs b/site/src/server.rs index ac8b353..64ad802 100644 --- a/site/src/server.rs +++ b/site/src/server.rs @@ -13,6 +13,7 @@ mod tarot; pub struct Handler { pub hostname: String, pub files: maj::server::files::Handler, + pub cgi: maj::server::cgi::Handler, } async fn dice(req: Request) -> Result { @@ -81,6 +82,7 @@ impl MajHandler for Handler { route!(req.url.path(), { (/"dice") => dice(req).await; (/"tools"/"character_gen") => tarot::character().await; + (/"cgi-bin"[/rest..]) => self.cgi.handle(req).await; }); self.files.handle(req).await diff --git a/src/client.rs b/src/client.rs index e1db0a6..12e2373 100644 --- a/src/client.rs +++ b/src/client.rs @@ -4,10 +4,7 @@ use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::TcpStream, }; -use tokio_rustls::{ - rustls::{TLSError}, - TlsConnector, -}; +use tokio_rustls::{rustls::TLSError, TlsConnector}; use url::Url; #[derive(thiserror::Error, Debug)] diff --git a/src/server/cgi.rs b/src/server/cgi.rs new file mode 100644 index 0000000..3ef9eff --- /dev/null +++ b/src/server/cgi.rs @@ -0,0 +1,72 @@ +/// 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 { + 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 { + 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 => "", + } + }; + + let filtered_env: HashMap = std::env::vars() + .filter(|&(ref k, _)| k == "TERM" || k == "TZ" || k == "LANG" || k == "PATH") + .collect(); + + let output = Command::new(path.clone()) + .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", "127.0.0.1") + .env("REMOTE_ADDR", "127.0.0.1") + .env("TLS_CIPHER", "Secure") + .env("TLS_VERSION", "TLSv1.3") + .output()?; + + let resp = Response::parse(&mut Cursor::new(output.stdout))?; + + Ok(resp) + } +} diff --git a/src/server/files.rs b/src/server/files.rs index c0c2b01..bceb1a7 100644 --- a/src/server/files.rs +++ b/src/server/files.rs @@ -12,9 +12,7 @@ pub struct Handler { 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, - } + Handler { base_dir: base_dir } } } diff --git a/src/server/mod.rs b/src/server/mod.rs index fb895b8..9cb3b56 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -34,6 +34,7 @@ enum RequestParsingError { mod routes; pub use routes::*; +pub mod cgi; pub mod files; #[async_trait] @@ -119,7 +120,9 @@ pub async fn write_header( status: StatusCode, meta: &str, ) -> Result { - stream.write(format!("{} {}\r\n", status as u8, meta).as_bytes()).await?; + stream + .write(format!("{} {}\r\n", status as u8, meta).as_bytes()) + .await?; Ok(()) } @@ -175,7 +178,14 @@ async fn handle( } Err(why) => { let _ = stream - .write(format!("{} {}\r\n", StatusCode::PermanentFailure as u8, why.to_string()).as_bytes()) + .write( + format!( + "{} {}\r\n", + StatusCode::PermanentFailure as u8, + why.to_string() + ) + .as_bytes(), + ) .await; log::error!("{}: {}: {:?}", addr, u, why); }