SCGI
This commit is contained in:
parent
5c7f0f2275
commit
e9f71f600d
6
README
6
README
@ -33,7 +33,11 @@ running:
|
||||
|
||||
Thanks to tiwesdaeg for figuring it out.
|
||||
|
||||
## CGI Environments
|
||||
## CGI and SCGI
|
||||
|
||||
There's example SCGI scripts for python and perl in the cgi-scripts directory.
|
||||
|
||||
### CGI Environments
|
||||
|
||||
In the configuration file there's "cgi" which is an optional bool to turn cgi
|
||||
on. If it's true it'll run scripts from any directory. To limit it to only one
|
||||
|
16
cgi-scripts/scgi/scgi.pl
Executable file
16
cgi-scripts/scgi/scgi.pl
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/local/bin/perl
|
||||
|
||||
|
||||
use SCGI;
|
||||
use IO::Socket;
|
||||
|
||||
my $socket = IO::Socket::INET->new(Listen => 5, ReuseAddr => 1, LocalPort => 4001)
|
||||
or die "cannot bind to port 4001: $!";
|
||||
|
||||
my $scgi = SCGI->new($socket, blocking => 1);
|
||||
|
||||
while (my $request = $scgi->accept) {
|
||||
$request->read_env;
|
||||
read $request->connection, my $body, $request->env->{CONTENT_LENGTH};
|
||||
print { $request->connection } "20\ttext/gemini\r\nperl\n";
|
||||
}
|
21
cgi-scripts/scgi/scgi.py
Executable file
21
cgi-scripts/scgi/scgi.py
Executable file
@ -0,0 +1,21 @@
|
||||
#! /usr/local/bin/python3.6
|
||||
import scgi
|
||||
import scgi.scgi_server
|
||||
|
||||
class TimeHandler(scgi.scgi_server.SCGIHandler):
|
||||
def produce(self, env, bodysize, input, output):
|
||||
header = "20\ttext/gemini\r\n"
|
||||
hi = "python\n"
|
||||
output.write(header.encode())
|
||||
output.write(hi.encode())
|
||||
|
||||
# Main program: create an SCGIServer object to
|
||||
# listen on port 4000. We tell the SCGIServer the
|
||||
# handler class that implements our application.
|
||||
server = scgi.scgi_server.SCGIServer(
|
||||
handler_class=TimeHandler,
|
||||
port=4000
|
||||
)
|
||||
# Tell our SCGIServer to start servicing requests.
|
||||
# This loops forever.
|
||||
server.serve()
|
@ -18,6 +18,8 @@ cgi = true
|
||||
# cgipath is optional and only checked if cgi is true. It restricts cgi to only
|
||||
# this directory.
|
||||
cgi = "/path/to/cgi-bin/"
|
||||
# scgi is optional
|
||||
scgi = { "/scgi" = "localhost:4000" }
|
||||
# cgienv is optional
|
||||
cgienv = { "GIT_PROJECT_ROOT" = "/srv/git" }
|
||||
# usrdir is optional. it'll look in /home/usr/public_gemini
|
||||
|
119
src/cgi.rs
119
src/cgi.rs
@ -3,43 +3,68 @@ use std::io;
|
||||
use std::path::PathBuf;
|
||||
use tokio::process::Command;
|
||||
use url::Url;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::config;
|
||||
use crate::conn;
|
||||
use crate::logger;
|
||||
use crate::status::Status;
|
||||
|
||||
fn envs(peer_addr: SocketAddr, srv: &config::ServerCfg, url: &url::Url) -> HashMap<String, String> {
|
||||
let mut envs = HashMap::new();
|
||||
envs.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
|
||||
envs.insert("GEMINI_URL".to_string(), url.to_string());
|
||||
envs.insert("SERVER_NAME".to_string(), url.host_str().unwrap().to_string());
|
||||
envs.insert("SERVER_PROTOCOL".to_string(), "GEMINI".to_string());
|
||||
let addr = peer_addr.ip().to_string();
|
||||
envs.insert("REMOTE_ADDR".to_string(), addr.clone());
|
||||
envs.insert("REMOTE_HOST".to_string(), addr);
|
||||
let port = peer_addr.port().to_string();
|
||||
envs.insert("REMOTE_PORT".to_string(), port);
|
||||
envs.insert("SERVER_SOFTWARE".to_string(), env!("CARGO_PKG_NAME").to_string());
|
||||
|
||||
if let Some(q) = url.query() {
|
||||
envs.insert("QUERY_STRING".to_string(), q.to_string());
|
||||
}
|
||||
|
||||
match &srv.server.cgienv {
|
||||
Some(c) => {
|
||||
for (k, v) in c.iter() {
|
||||
envs.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
envs
|
||||
}
|
||||
|
||||
fn check(byt: u8, peer_addr: SocketAddr, u: url::Url) -> bool {
|
||||
match byt {
|
||||
49 => {
|
||||
logger::logger(peer_addr, Status::Input, u.as_str());
|
||||
},
|
||||
50 => {
|
||||
logger::logger(peer_addr, Status::Success, u.as_str());
|
||||
},
|
||||
51..=54 => {}
|
||||
_ => {
|
||||
logger::logger(peer_addr, Status::CGIError, u.as_str());
|
||||
return false;
|
||||
},
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn cgi(
|
||||
mut con: conn::Connection,
|
||||
srv: &config::ServerCfg,
|
||||
path: PathBuf,
|
||||
url: Url,
|
||||
) -> Result<(), io::Error> {
|
||||
let mut envs = HashMap::new();
|
||||
envs.insert("GATEWAY_INTERFACE", "CGI/1.1");
|
||||
envs.insert("GEMINI_URL", url.as_str());
|
||||
envs.insert("SERVER_NAME", url.host_str().unwrap());
|
||||
envs.insert("SCRIPT_NAME", path.file_name().unwrap().to_str().unwrap());
|
||||
envs.insert("SERVER_PROTOCOL", "GEMINI");
|
||||
let addr = con.peer_addr.ip().to_string();
|
||||
envs.insert("REMOTE_ADDR", &addr);
|
||||
envs.insert("REMOTE_HOST", &addr);
|
||||
let port = con.peer_addr.port().to_string();
|
||||
envs.insert("REMOTE_PORT", &port);
|
||||
envs.insert("SERVER_SOFTWARE", env!("CARGO_PKG_NAME"));
|
||||
|
||||
if let Some(q) = url.query() {
|
||||
envs.insert("QUERY_STRING", q);
|
||||
}
|
||||
|
||||
match &srv.server.cgienv {
|
||||
Some(c) => {
|
||||
for (k, v) in c.iter() {
|
||||
envs.insert(&k, &v);
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
let mut envs = envs(con.peer_addr, srv, &url);
|
||||
envs.insert("SCRIPT_NAME".to_string(), path.file_name().unwrap().to_str().unwrap().to_string());
|
||||
|
||||
let cmd = Command::new(path.to_str().unwrap())
|
||||
.env_clear()
|
||||
@ -71,7 +96,49 @@ pub async fn cgi(
|
||||
return Ok(());
|
||||
}
|
||||
let cmd = String::from_utf8(cmd.stdout).unwrap();
|
||||
logger::logger(con.peer_addr, Status::Success, url.as_str());
|
||||
if !check(cmd.as_bytes()[0], con.peer_addr, url) {
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
con.send_raw(cmd.as_bytes()).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection, srv: &config::ServerCfg) -> Result<(), io::Error> {
|
||||
let addr = addr
|
||||
.to_socket_addrs()?
|
||||
.next()
|
||||
.ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?;
|
||||
|
||||
let mut stream = match TcpStream::connect(&addr).await {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
logger::logger(con.peer_addr, Status::CGIError, u.as_str());
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let envs = envs(con.peer_addr, srv, &u);
|
||||
let len = 0usize;
|
||||
let mut byt = String::from(format!("CONTENT_LENGTH\x00{}\x00SCGI\x001\x00
|
||||
RQUEST_METHOD\x00POST\x00REQUEST_URI\x00{}\x00", len, u.path()));
|
||||
for (k, v) in envs.iter() {
|
||||
byt.push_str(&format!("{}\x00{}\x00", k, v));
|
||||
}
|
||||
byt = byt.len().to_string() + ":" + &byt + ",";
|
||||
|
||||
stream.write_all(byt.as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
let mut buf = vec![];
|
||||
stream.read_to_end(&mut buf).await?;
|
||||
let req = String::from_utf8_lossy(&buf[..]);
|
||||
if !check(req.as_bytes()[0], con.peer_addr, u) {
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
con.send_raw(req.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ pub struct Server {
|
||||
pub usrdir: Option<bool>,
|
||||
pub proxy: Option<HashMap<String, String>>,
|
||||
pub redirect: Option<HashMap<String, String>>,
|
||||
pub scgi: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
15
src/main.rs
15
src/main.rs
@ -180,6 +180,21 @@ async fn handle_connection(
|
||||
None => {}
|
||||
}
|
||||
|
||||
match &srv.server.scgi {
|
||||
Some(sc) => {
|
||||
let u = url.path().trim_end_matches("/");
|
||||
match sc.get(u) {
|
||||
Some(r) => {
|
||||
cgi::scgi(r.to_string(), url, con, srv).await?;
|
||||
return Ok(());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
},
|
||||
None => {},
|
||||
}
|
||||
|
||||
|
||||
let mut path = PathBuf::new();
|
||||
|
||||
if url.path().starts_with("/~") && srv.server.usrdir.unwrap_or(false) {
|
||||
|
Loading…
Reference in New Issue
Block a user