use super::Response;
use {
super::Request,
crate::{config::Server, response::ServerError, CONFIG},
std::{
fs::File,
io::{self, Write},
path::{Path, PathBuf},
process::{Command, Output},
},
};
pub struct Cgi {
document_root: String,
query_string: String,
remote_addr: String,
request_uri: String,
script_filename: String,
script_name: String,
server_name: String,
server_port: String,
server_software: String,
body: Option<Vec<u8>>,
}
impl Cgi {
pub fn new(request: Request, server: &Server, dir: &Path) -> Result<Self, ServerError> {
let base = match PathBuf::from(&request.path).strip_prefix(dir) {
Ok(b) => b.to_path_buf(),
Err(_) => return Err(ServerError::CgiError),
};
let mut parts = base.components();
let script_base = match parts.next() {
Some(s) => s,
None => return Err(ServerError::CgiError),
};
let mut script_name = dir.to_path_buf();
script_name.push(script_base);
let mut script_filename = server.root.clone();
script_filename.push(script_name.strip_prefix("/").unwrap_or(&script_name));
let query_string = match &request.query {
Some(q) => q.to_string(),
None => String::new(),
};
let server_software = format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
Ok(Self {
document_root: format!("{}", server.root.display()),
query_string,
remote_addr: format!("{}", request.client_ip),
request_uri: format!(
"{}{}",
&request.path,
request
.query
.as_ref()
.map_or("", std::string::String::as_str)
),
script_filename: format!("{}", script_filename.display()),
script_name: format!("{}", script_name.display()),
server_name: server.name.clone(),
server_port: CONFIG.address.port.clone(),
server_software,
body: request.content,
})
}
pub fn from_script_alias(
request: Request,
server: &Server,
script_alias: &Path,
) -> Result<Self, ServerError> {
let script_name = match script_alias.file_name() {
Some(name) => name.to_string_lossy(),
None => return Err(ServerError::CgiError),
};
let mut script_filename = server.root.clone();
script_filename.push(script_alias.strip_prefix("/").unwrap_or(script_alias));
let query_string = match &request.query {
Some(q) => q.to_string(),
None => String::new(),
};
let server_software = format!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
Ok(Self {
document_root: format!("{}", server.root.display()),
query_string,
remote_addr: format!("{}", request.client_ip),
request_uri: match request.query {
Some(q) => format!("{}?{q}", &request.path),
None => request.path.to_string(),
},
script_filename: format!("{}", script_filename.display()),
script_name: format!("{script_name}"),
server_name: server.name.clone(),
server_port: CONFIG.address.port.clone(),
server_software,
body: request.content,
})
}
pub fn run(&self) -> io::Result<Output> {
let dir = tempfile::tempdir()?;
let tmpfile = match self.body.as_ref() {
Some(body) => {
let path = dir.path().join("body");
let mut fd = File::create(&path)?;
fd.write_all(body)?;
path.display().to_string()
}
None => String::new(),
};
Command::new(&self.script_filename)
.env_clear()
.envs([
("PATH", "/usr/local/bin:/usr/bin:/bin"),
("DOCUMENT_ROOT", &self.document_root),
("QUERY_STRING", &self.query_string),
("REMOTE_ADDR", &self.remote_addr),
("REQUEST_URI", &self.request_uri),
("SCRIPT_FILENAME", &self.script_filename),
("SCRIPT_NAME", &self.script_name),
("SERVER_NAME", &self.server_name),
("SERVER_PORT", &self.server_port),
("SERVER_SOFTWARE", &self.server_software),
("REQUEST_BODY", &tmpfile),
])
.output()
}
}
impl From<Cgi> for Response {
fn from(cgi: Cgi) -> Self {
match cgi.run() {
Ok(output) => {
let idx = match output.stdout.iter().position(|&x| x == b'\n') {
Some(i) => i,
None => return ServerError::CgiError.into(),
};
let mimetype = String::from_utf8_lossy(&output.stdout[0..idx]);
let body = Vec::from(&output.stdout[idx + 1..]);
Self::Success {
mimetype: mimetype.to_string(),
body,
}
}
Err(_) => ServerError::CgiError.into(),
}
}
}