summaryrefslogtreecommitdiff
path: root/src/modules
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
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')
-rw-r--r--src/modules/cgi.rs142
-rw-r--r--src/modules/mod.rs34
-rw-r--r--src/modules/switch.rs4
3 files changed, 159 insertions, 21 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),
+ ))
+ }))
+ })
+ }
+}
diff --git a/src/modules/mod.rs b/src/modules/mod.rs
index 00425bf..987646f 100644
--- a/src/modules/mod.rs
+++ b/src/modules/mod.rs
@@ -1,24 +1,15 @@
use crate::error::ServiceError;
use crate::State;
-use accesslog::AccessLogKind;
-use auth::{basic::HttpBasicAuthKind, cookie::CookieAuthKind};
use bytes::Bytes;
-use error::ErrorKind;
-use file::FileKind;
-use files::FilesKind;
use futures::Future;
-use headers::HeadersKind;
-use hosts::HostsKind;
use http_body_util::combinators::BoxBody;
use hyper::{body::Incoming, Request, Response};
-use proxy::ProxyKind;
-use redirect::RedirectKind;
use serde_yaml::Value;
use std::{net::SocketAddr, pin::Pin, sync::Arc};
-use switch::SwitchKind;
pub mod accesslog;
pub mod auth;
+pub mod cgi;
pub mod error;
pub mod file;
pub mod files;
@@ -32,17 +23,18 @@ pub type NodeRequest = Request<Incoming>;
pub type NodeResponse = Response<BoxBody<Bytes, ServiceError>>;
pub static MODULES: &[&dyn NodeKind] = &[
- &HttpBasicAuthKind,
- &CookieAuthKind,
- &ProxyKind,
- &HostsKind,
- &FilesKind,
- &FileKind,
- &AccessLogKind,
- &ErrorKind,
- &HeadersKind,
- &SwitchKind,
- &RedirectKind,
+ &auth::basic::HttpBasicAuthKind,
+ &auth::cookie::CookieAuthKind,
+ &proxy::ProxyKind,
+ &hosts::HostsKind,
+ &files::FilesKind,
+ &file::FileKind,
+ &accesslog::AccessLogKind,
+ &error::ErrorKind,
+ &headers::HeadersKind,
+ &switch::SwitchKind,
+ &redirect::RedirectKind,
+ &cgi::CgiKind,
];
pub struct NodeContext {
diff --git a/src/modules/switch.rs b/src/modules/switch.rs
index bbb9e98..943a81d 100644
--- a/src/modules/switch.rs
+++ b/src/modules/switch.rs
@@ -47,6 +47,8 @@ impl Node for Switch {
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
enum Condition {
+ Any(Vec<Condition>),
+ All(Vec<Condition>),
IsWebsocketUpgrade,
IsPost,
IsGet,
@@ -66,6 +68,8 @@ impl Condition {
Condition::PathIs(path) => req.uri().path() == path,
Condition::IsPost => req.method() == Method::POST,
Condition::IsGet => req.method() == Method::GET,
+ Condition::Any(conds) => conds.iter().any(|c| c.test(req)),
+ Condition::All(conds) => conds.iter().all(|c| c.test(req)),
}
}
}