aboutsummaryrefslogtreecommitdiff
path: root/src/modules/cgi.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-08-12 20:49:47 +0200
committermetamuffin <metamuffin@disroot.org>2024-08-12 20:49:47 +0200
commitef8172874f650078e8cfb6e1582de4ece5495640 (patch)
tree35ced9080db86739904e903a68999fd1606bc9b1 /src/modules/cgi.rs
parentb28c5418b0635bf2fc3b0d18922df4ebb7cccd57 (diff)
downloadgnix-ef8172874f650078e8cfb6e1582de4ece5495640.tar
gnix-ef8172874f650078e8cfb6e1582de4ece5495640.tar.bz2
gnix-ef8172874f650078e8cfb6e1582de4ece5495640.tar.zst
add any/all conditions and experimental CGI support
Diffstat (limited to 'src/modules/cgi.rs')
-rw-r--r--src/modules/cgi.rs142
1 files changed, 142 insertions, 0 deletions
diff --git a/src/modules/cgi.rs b/src/modules/cgi.rs
new file mode 100644
index 0000000..b6f1033
--- /dev/null
+++ b/src/modules/cgi.rs
@@ -0,0 +1,142 @@
+use super::{Node, NodeKind, NodeResponse};
+use crate::error::ServiceError;
+use anyhow::{anyhow, Result};
+use futures::TryStreamExt;
+use http_body_util::{combinators::BoxBody, StreamBody};
+use hyper::{
+ body::Frame,
+ header::{HeaderName, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
+ Response, StatusCode,
+};
+use serde::Deserialize;
+use serde_yaml::Value;
+use std::{future::Future, path::PathBuf, pin::Pin, process::Stdio, str::FromStr, sync::Arc};
+use tokio::{
+ io::{AsyncBufReadExt, BufReader},
+ process::Command,
+};
+use tokio_util::io::ReaderStream;
+use users::get_user_by_name;
+
+pub struct CgiKind;
+
+#[derive(Deserialize)]
+struct CgiConfig {
+ bin: PathBuf,
+ user: Option<String>,
+}
+struct Cgi {
+ config: CgiConfig,
+ user: Option<u32>,
+}
+
+impl NodeKind for CgiKind {
+ fn name(&self) -> &'static str {
+ "cgi"
+ }
+ fn instanciate(&self, config: Value) -> Result<Arc<dyn Node>> {
+ Ok(Arc::new(Cgi::new(serde_yaml::from_value::<CgiConfig>(
+ config,
+ )?)?))
+ }
+}
+impl Cgi {
+ pub fn new(config: CgiConfig) -> Result<Self> {
+ Ok(Self {
+ user: config
+ .user
+ .as_ref()
+ .map(|u| {
+ get_user_by_name(u)
+ .map(|u| u.uid())
+ .ok_or(anyhow!("user does not exist"))
+ })
+ .transpose()?,
+ config,
+ })
+ }
+}
+impl Node for Cgi {
+ fn handle<'a>(
+ &'a self,
+ context: &'a mut super::NodeContext,
+ request: super::NodeRequest,
+ ) -> Pin<Box<dyn Future<Output = Result<NodeResponse, ServiceError>> + Send + Sync + 'a>> {
+ Box::pin(async move {
+ let mut command = Command::new(&self.config.bin);
+ command.stdin(Stdio::piped());
+ command.stdout(Stdio::piped());
+ command.stderr(Stdio::inherit());
+ if let Some(uid) = self.user {
+ command.uid(uid);
+ }
+
+ // command.env("AUTH_TYPE", );
+ command.env(
+ "CONTENT_LENGTH",
+ request
+ .headers()
+ .get(CONTENT_LENGTH)
+ .map(|x| x.to_str().ok())
+ .flatten()
+ .unwrap_or_default(),
+ );
+ command.env(
+ "CONTENT_TYPE",
+ request
+ .headers()
+ .get(CONTENT_TYPE)
+ .map(|x| x.to_str().ok())
+ .flatten()
+ .unwrap_or_default(),
+ );
+ command.env("GATEWAY_INTERFACE", "CGI/1.1");
+ command.env("PATH_INFO", request.uri().path());
+ command.env("PATH_TRANSLATED", request.uri().path());
+ command.env("QUERY_STRING", request.uri().query().unwrap_or_default());
+ command.env("REMOTE_ADDR", context.addr.to_string());
+ // command.env("REMOTE_HOST", ));
+ // command.env("REMOTE_IDENT", ));
+ // command.env("REMOTE_USER", ));
+ command.env("REQUEST_METHOD", request.method().to_string());
+ // command.env("SCRIPT_NAME", );
+ // command.env("SERVER_NAME", );
+ // command.env("SERVER_PORT", );
+ command.env("SERVER_PROTOCOL", "HTTP/1.1");
+ command.env("SERVER_SOFTWARE", "gnix");
+
+ let mut child = command.spawn()?;
+ let mut stdout = BufReader::new(child.stdout.take().unwrap());
+ let mut line = String::new();
+ let mut response = Response::new(());
+ loop {
+ line.clear();
+ stdout.read_line(&mut line).await?;
+ let line = line.trim();
+ if line.is_empty() {
+ break;
+ }
+ let (key, value) = line.split_once(":").ok_or(ServiceError::Other)?;
+ let value = value.trim();
+ if key == "Status" {
+ *response.status_mut() = StatusCode::from_u16(
+ value.split_once(" ").unwrap_or((value, "")).0.parse()?,
+ )
+ .map_err(|_| ServiceError::InvalidHeader)?;
+ } else {
+ response.headers_mut().insert(
+ HeaderName::from_str(key).map_err(|_| ServiceError::InvalidHeader)?,
+ HeaderValue::from_str(value).map_err(|_| ServiceError::InvalidHeader)?,
+ );
+ }
+ }
+ Ok(response.map(|()| {
+ BoxBody::new(StreamBody::new(
+ ReaderStream::new(stdout)
+ .map_ok(Frame::data)
+ .map_err(ServiceError::Io),
+ ))
+ }))
+ })
+ }
+}