add CGI support
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
ccb142d8b3
commit
1da65dcfeb
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "20 text/plain"
|
||||||
|
echo "The following is the CGI environment of this program:"
|
||||||
|
echo
|
||||||
|
env
|
|
@ -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),
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue