about summary refs log tree commit diff
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2025-03-11 19:02:00 -0500
committergennyble <gen@nyble.dev>2025-03-11 19:02:00 -0500
commit67e7e572e814b1cfcb1580d19a419a1785a2a9ae (patch)
tree2dd26b22c80d23af0cdb9e5d743ffa6115299c78
parent352bc4fe6387a428dff3390476a57bf88a96d228 (diff)
downloadcorgi-67e7e572e814b1cfcb1580d19a419a1785a2a9ae.tar.gz
corgi-67e7e572e814b1cfcb1580d19a419a1785a2a9ae.zip
set more env vars
-rw-r--r--README.md7
-rw-r--r--src/main.rs76
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();