add CGI support
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Cadey Ratio 2020-08-08 11:23:36 -04:00
parent ccb142d8b3
commit 1da65dcfeb
8 changed files with 100 additions and 10 deletions

View File

@ -1,11 +1,11 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use atom_syndication as atom; use atom_syndication as atom;
use chrono::{prelude::*, Duration};
use maj::gemini::Node; use maj::gemini::Node;
use rustls::ClientConfig; use rustls::ClientConfig;
use std::io::{self, BufReader, Cursor, Write}; use std::io::{self, BufReader, Cursor, Write};
use std::ops::Sub; use std::ops::Sub;
use std::str; use std::str;
use chrono::{Duration, prelude::*};
mod tls; mod tls;

6
site/cgi-bin/env.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
echo "20 text/plain"
echo "The following is the CGI environment of this program:"
echo
env

View File

@ -37,6 +37,10 @@ struct Options {
#[structopt(short = "s", long, env = "STATIC_PATH", default_value = "./static")] #[structopt(short = "s", long, env = "STATIC_PATH", default_value = "./static")]
static_path: PathBuf, static_path: PathBuf,
/// CGI path
#[structopt(short = "C", long, env = "CGI_PATH", default_value = "./cgi-bin")]
cgi_path: PathBuf,
/// server hostname /// server hostname
#[structopt( #[structopt(
long = "hostname", long = "hostname",
@ -83,6 +87,7 @@ fn main() -> Result<(), maj::server::Error> {
let h = Arc::new(server::Handler { let h = Arc::new(server::Handler {
hostname: opts.hostname, hostname: opts.hostname,
files: maj::server::files::Handler::new(opts.static_path), files: maj::server::files::Handler::new(opts.static_path),
cgi: maj::server::cgi::Handler::new(opts.cgi_path),
}); });
{ {

View File

@ -13,6 +13,7 @@ mod tarot;
pub struct Handler { pub struct Handler {
pub hostname: String, pub hostname: String,
pub files: maj::server::files::Handler, pub files: maj::server::files::Handler,
pub cgi: maj::server::cgi::Handler,
} }
async fn dice(req: Request) -> Result<Response, Error> { async fn dice(req: Request) -> Result<Response, Error> {
@ -81,6 +82,7 @@ impl MajHandler for Handler {
route!(req.url.path(), { route!(req.url.path(), {
(/"dice") => dice(req).await; (/"dice") => dice(req).await;
(/"tools"/"character_gen") => tarot::character().await; (/"tools"/"character_gen") => tarot::character().await;
(/"cgi-bin"[/rest..]) => self.cgi.handle(req).await;
}); });
self.files.handle(req).await self.files.handle(req).await

View File

@ -4,10 +4,7 @@ use tokio::{
io::{AsyncReadExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream, net::TcpStream,
}; };
use tokio_rustls::{ use tokio_rustls::{rustls::TLSError, TlsConnector};
rustls::{TLSError},
TlsConnector,
};
use url::Url; use url::Url;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]

72
src/server/cgi.rs Normal file
View File

@ -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<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 => "",
}
};
let filtered_env: HashMap<String, String> = 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)
}
}

View File

@ -12,9 +12,7 @@ pub struct Handler {
impl Handler { impl Handler {
/// Serves static files from an OS directory with a given prefix chopped off. /// Serves static files from an OS directory with a given prefix chopped off.
pub fn new(base_dir: PathBuf) -> Self { pub fn new(base_dir: PathBuf) -> Self {
Handler { Handler { base_dir: base_dir }
base_dir: base_dir,
}
} }
} }

View File

@ -34,6 +34,7 @@ enum RequestParsingError {
mod routes; mod routes;
pub use routes::*; pub use routes::*;
pub mod cgi;
pub mod files; pub mod files;
#[async_trait] #[async_trait]
@ -119,7 +120,9 @@ pub async fn write_header<W: Write + Unpin>(
status: StatusCode, status: StatusCode,
meta: &str, meta: &str,
) -> Result { ) -> 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(()) Ok(())
} }
@ -175,7 +178,14 @@ async fn handle<T>(
} }
Err(why) => { Err(why) => {
let _ = stream 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; .await;
log::error!("{}: {}: {:?}", addr, u, why); log::error!("{}: {}: {:?}", addr, u, why);
} }