diff options
author | gennyble <gen@nyble.dev> | 2025-03-11 19:02:00 -0500 |
---|---|---|
committer | gennyble <gen@nyble.dev> | 2025-03-11 19:02:00 -0500 |
commit | 67e7e572e814b1cfcb1580d19a419a1785a2a9ae (patch) | |
tree | 2dd26b22c80d23af0cdb9e5d743ffa6115299c78 | |
parent | 352bc4fe6387a428dff3390476a57bf88a96d228 (diff) | |
download | corgi-67e7e572e814b1cfcb1580d19a419a1785a2a9ae.tar.gz corgi-67e7e572e814b1cfcb1580d19a419a1785a2a9ae.zip |
set more env vars
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | src/main.rs | 76 |
2 files changed, 64 insertions, 19 deletions
diff --git a/README.md b/README.md index ff4066d..ef2dd2b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,14 @@ Sets the following environmental variables for the CGI script, many following [R - **`GATEWAY_INTERFACE`** to the fixed value `CGI/1.1` - **`PATH_INFO`** to the HTTP path the client requested - **`QUERY_STRING`** to the query part of the URI +- **`REMOTE_ADDR`** is the `Forwarded-For` header, maybe prefixed with + an `X-`, or the client address if that header is not set - **`REQUEST_METHOD`** to the HTTP request method +- **`SCRIPT_NAME`** is set to the path specified in the config +- **`SERVER_NAME`** is set to the HTTP host header +- **`SERVER_PORT`** is the port that corgi is running on +- **`SERVER_PROTOCOL`** is the HTTP version string that the request came in on +- **`SERVER_SOFTWARE`** is `corgi/1.0` where `1.0` is the current version number Additionally, corgi will set environment variables for the HTTP request headers. They will be uppercased and hyphens replaced with underscores. diff --git a/src/main.rs b/src/main.rs index c83c0bd..494214e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,16 @@ use core::panic; -use std::{net::SocketAddr, pin::Pin}; +use std::{ + net::{IpAddr, SocketAddr}, + pin::Pin, + str::FromStr, +}; use confindent::Confindent; use http_body_util::{BodyExt, Full}; use hyper::{ HeaderMap, Request, Response, StatusCode, body::{Body, Bytes, Incoming}, + header::{HeaderName, HeaderValue, LAST_MODIFIED}, server::conn::http1, service::Service, }; @@ -14,6 +19,7 @@ use tokio::{net::TcpListener, process::Command, runtime::Runtime}; #[derive(Clone, Debug)] pub struct Settings { + port: u16, script_filename: String, env: Vec<(String, String)>, } @@ -30,6 +36,7 @@ fn main() { .map(|e| e.values().map(|v| (v.key_owned(), v.value_owned().unwrap())).collect()); let settings = Settings { + port: 26744, script_filename: script.value_owned().expect("'Script' key has no value'"), env: env.unwrap_or_default(), }; @@ -39,15 +46,19 @@ fn main() { } async fn run(settings: Settings) { - let addr = SocketAddr::from(([0, 0, 0, 0], 2562)); + let addr = SocketAddr::from(([0, 0, 0, 0], settings.port)); let listen = TcpListener::bind(addr).await.unwrap(); - let svc = Svc { settings }; + let svc = Svc { + settings, + client_addr: addr, + }; loop { - let (stream, _caddr) = listen.accept().await.unwrap(); + let (stream, caddr) = listen.accept().await.unwrap(); let io = TokioIo::new(stream); - let svc_clone = svc.clone(); + let mut svc_clone = svc.clone(); + svc_clone.client_addr = caddr; tokio::task::spawn( async move { http1::Builder::new().serve_connection(io, svc_clone).await }, ); @@ -57,6 +68,7 @@ async fn run(settings: Settings) { #[derive(Clone, Debug)] struct Svc { settings: Settings, + client_addr: SocketAddr, } impl Service<Request<Incoming>> for Svc { @@ -70,27 +82,55 @@ impl Service<Request<Incoming>> for Svc { } let settings = self.settings.clone(); - Box::pin(async { Ok(Self::handle(settings, req).await) }) + let caddr = self.client_addr; + Box::pin(async move { Ok(Self::handle(settings, caddr, req).await) }) } } impl Svc { - async fn handle(settings: Settings, req: Request<Incoming>) -> Response<Full<Bytes>> { + async fn handle( + settings: Settings, + caddr: SocketAddr, + req: Request<Incoming>, + ) -> Response<Full<Bytes>> { + // Collect things we need from the request before we eat it's body let method = req.method().as_str().to_ascii_uppercase(); + let version = req.version(); let path = req.uri().path().to_owned(); let query = req.uri().query().unwrap_or_default().to_owned(); let headers = req.headers().clone(); let body = req.into_body().collect().await.unwrap().to_bytes(); let content_length = body.len(); - // Not setting PATH_TRANSLATED + // Find the client address + let client_addr = { + let x_forward = Self::parse_addr_from_header(headers.get("x-forwarded-for")); + let forward = Self::parse_addr_from_header(headers.get("forwarded-for")); + + forward.unwrap_or(x_forward.unwrap_or(caddr.ip())) + }; + + let server_name = headers + .get("Host") + .expect("no http host header set") + .to_str() + .expect("failed to decode http host as string"); let mut cmd = Command::new(&settings.script_filename); cmd.env("GATEWAY_INTERFACE", "CGI/1.1") - .env("SCRIPT_NAME", settings.script_filename) .env("PATH_INFO", path) .env("QUERY_STRING", query) - .env("REQUEST_METHOD", method); + .env("REMOTE_ADDR", client_addr.to_string()) + .env("REQUEST_METHOD", method) + .env("SCRIPT_NAME", settings.script_filename) + .env("SERVER_NAME", server_name) + .env("SERVER_PORT", settings.port.to_string()) + .env("SERVER_PROTOCOL", format!("{:?}", version)) + .env("SERVER_SOFTWARE", Self::SERVER_SOFTWARE); + + if content_length > 0 { + cmd.env("CONTENT_LENGTH", content_length.to_string()); + } // Set env associated with the HTTP request headers Self::set_http_env(headers, &mut cmd); @@ -101,14 +141,9 @@ impl Svc { cmd.env(key.to_ascii_uppercase(), value); } - /*if content_length > 0 { - cmd.env("CONTENT_LENGTH", content_length.to_string()); - }*/ - let cgi_response = Self::call_and_parse_cgi(cmd).await; - - let mut response = - Response::builder().status(StatusCode::from_u16(cgi_response.status).unwrap()); + let status = StatusCode::from_u16(cgi_response.status).unwrap(); + let mut response = Response::builder().status(status); for (key, value) in cgi_response.headers { response = response.header(key, value); @@ -117,10 +152,13 @@ impl Svc { response.body(Full::new(Bytes::from(body.to_vec()))).unwrap() } - fn make<B: Into<Bytes>>(b: B) -> Response<Full<Bytes>> { - Response::builder().body(Full::new(b.into())).unwrap() + fn parse_addr_from_header(maybe_hval: Option<&HeaderValue>) -> Option<IpAddr> { + maybe_hval.map(|h| h.to_str().ok()).flatten().map(|s| s.parse().ok()).flatten() } + const SERVER_SOFTWARE: &'static str = + concat!(env!("CARGO_PKG_NAME"), '/', env!("CARGO_PKG_VERSION")); + fn set_http_env(headers: HeaderMap, cmd: &mut Command) { for (key, value) in headers.iter() { let key_str = key.as_str(); |