use super::{Node, NodeKind, NodeResponse}; use crate::error::ServiceError; use anyhow::{anyhow, Result}; use futures::TryStreamExt; use http_body_util::{combinators::BoxBody, BodyExt, 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, io::ErrorKind, path::PathBuf, pin::Pin, process::Stdio, str::FromStr, sync::Arc, }; use tokio::{ io::{copy, AsyncBufReadExt, BufReader, BufWriter}, process::Command, spawn, }; use tokio_util::io::{ReaderStream, StreamReader}; use users::get_user_by_name; pub struct CgiKind; #[derive(Deserialize)] struct CgiConfig { bin: PathBuf, user: Option, } struct Cgi { config: CgiConfig, user: Option, } impl NodeKind for CgiKind { fn name(&self) -> &'static str { "cgi" } fn instanciate(&self, config: Value) -> Result> { Ok(Arc::new(Cgi::new(serde_yaml::from_value::( config, )?)?)) } } impl Cgi { pub fn new(config: CgiConfig) -> Result { 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> + 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 stdin = BufWriter::new(child.stdin.take().unwrap()); // TODO prevent abuse let mut body = StreamReader::new( request .into_body() .into_data_stream() .map_err(|_| std::io::Error::new(ErrorKind::BrokenPipe, "asd")), ); spawn(async move { copy(&mut body, &mut stdin).await }); 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), )) })) }) } }