Add CGI env parameter PATH_INFO
This means that a CGI script, say, /cgi-bin/foo can be requested using a subpath like /cgi-bin/foo/bar/baz. In that case, PATH_INFO is set to "/bar/baz". As it is easy to compute SCRIPT_NAME along with PATH_INFO, we also refactor it. SCRIPT_NAME now contains the URL to the script, not just the basename of it. This is typical CGI behavior. Since the cgi handling code was needed in two places, it was refactored a bit. Other parts of handle_connection could be refactored similarly. The code now supports executables inside subdirectories of cgipath. Previously the executable had to be in cgipath, subdirs not allowed.
This commit is contained in:
committed by
int 80h
parent
ecba9a266d
commit
e0e426694b
19
src/cgi.rs
19
src/cgi.rs
@@ -50,7 +50,7 @@ fn envs(peer_addr: SocketAddr, srv: &config::ServerCfg, url: &url::Url) -> HashM
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "cgi", feature = "scgi"))]
|
||||
fn check(byt: u8, peer_addr: SocketAddr, u: url::Url) -> bool {
|
||||
fn check(byt: u8, peer_addr: SocketAddr, u: &url::Url) -> bool {
|
||||
match byt {
|
||||
49 => {
|
||||
logger::logger(peer_addr, Status::Input, u.as_str());
|
||||
@@ -69,13 +69,16 @@ fn check(byt: u8, peer_addr: SocketAddr, u: url::Url) -> bool {
|
||||
|
||||
#[cfg(feature = "cgi")]
|
||||
pub async fn cgi(
|
||||
mut con: conn::Connection,
|
||||
con: &mut conn::Connection,
|
||||
srv: &config::ServerCfg,
|
||||
path: PathBuf,
|
||||
url: url::Url,
|
||||
url: &url::Url,
|
||||
script_name: String,
|
||||
path_info: String
|
||||
) -> Result<(), io::Error> {
|
||||
let mut envs = envs(con.peer_addr, srv, &url);
|
||||
envs.insert("SCRIPT_NAME".to_string(), path.file_name().unwrap().to_str().unwrap().to_string());
|
||||
envs.insert("SCRIPT_NAME".into(), script_name);
|
||||
envs.insert("PATH_INFO".into(), path_info);
|
||||
|
||||
match path.parent() {
|
||||
Some(p) => {
|
||||
@@ -88,7 +91,7 @@ pub async fn cgi(
|
||||
.env_clear()
|
||||
.envs(&envs)
|
||||
.output();
|
||||
|
||||
|
||||
let cmd = match tokio::time::timeout(tokio::time::Duration::from_secs(5), cmd).await {
|
||||
Ok(c) => {
|
||||
match c {
|
||||
@@ -118,7 +121,7 @@ pub async fn cgi(
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
con.send_raw(cmd.as_bytes()).await?;
|
||||
return Ok(());
|
||||
}
|
||||
@@ -146,7 +149,7 @@ pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection, srv: &co
|
||||
byt.push_str(&format!("{}\x00{}\x00", k, v));
|
||||
}
|
||||
byt = byt.len().to_string() + ":" + &byt + ",";
|
||||
|
||||
|
||||
stream.write_all(byt.as_bytes()).await?;
|
||||
stream.flush().await?;
|
||||
|
||||
@@ -158,7 +161,7 @@ pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection, srv: &co
|
||||
return Ok(());
|
||||
}
|
||||
let req = String::from_utf8_lossy(&buf[..]);
|
||||
if !check(req.as_bytes()[0], con.peer_addr, u) {
|
||||
if !check(req.as_bytes()[0], con.peer_addr, &u) {
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
82
src/main.rs
82
src/main.rs
@@ -105,6 +105,58 @@ async fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
|
||||
return Ok(list);
|
||||
}
|
||||
|
||||
// Handle CGI and return Ok(true), or indicate this request wasn't for CGI with Ok(false)
|
||||
#[cfg(feature = "cgi")]
|
||||
async fn handle_cgi(
|
||||
con: &mut conn::Connection,
|
||||
srv: &config::ServerCfg,
|
||||
request: &str,
|
||||
url: &Url,
|
||||
full_path: &PathBuf,
|
||||
) -> Result<bool, io::Error> {
|
||||
if srv.server.cgi.unwrap_or(false) {
|
||||
let mut path = full_path.clone();
|
||||
let mut segments = url.path_segments().unwrap();
|
||||
let mut path_info = "".to_string();
|
||||
|
||||
// Find an ancestor url that matches a file
|
||||
while !path.exists() {
|
||||
if let Some(segment) = segments.next_back() {
|
||||
path.pop();
|
||||
path_info = format!("/{}{}", &segment, path_info);
|
||||
} else {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
let script_name = format!("/{}", segments.collect::<Vec<_>>().join("/"));
|
||||
|
||||
let meta = tokio::fs::metadata(&path).await?;
|
||||
let perm = meta.permissions();
|
||||
|
||||
match &srv.server.cgipath {
|
||||
Some(c) => {
|
||||
if path.starts_with(c) {
|
||||
if perm.mode() & 0o0111 == 0o0111 {
|
||||
cgi::cgi(con, srv, path, url, script_name, path_info).await?;
|
||||
return Ok(true);
|
||||
} else {
|
||||
logger::logger(con.peer_addr, Status::CGIError, request);
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
|
||||
cgi::cgi(con, srv, path, url, script_name, path_info).await?;
|
||||
return Ok(true);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
// TODO Rewrite this monster.
|
||||
async fn handle_connection(
|
||||
mut con: conn::Connection,
|
||||
@@ -237,6 +289,12 @@ async fn handle_connection(
|
||||
}
|
||||
|
||||
if !path.exists() {
|
||||
// See if it's a subpath of a CGI script before returning NotFound
|
||||
#[cfg(feature = "cgi")]
|
||||
if handle_cgi(&mut con, srv, &request, &url, &path).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
logger::logger(con.peer_addr, Status::NotFound, &request);
|
||||
con.send_status(Status::NotFound, None).await?;
|
||||
return Ok(());
|
||||
@@ -272,28 +330,10 @@ async fn handle_connection(
|
||||
}
|
||||
|
||||
#[cfg(feature = "cgi")]
|
||||
if srv.server.cgi.unwrap_or(false) {
|
||||
match &srv.server.cgipath {
|
||||
Some(c) => {
|
||||
if c.trim_end_matches("/") == path.parent().unwrap().to_str().unwrap() {
|
||||
if perm.mode() & 0o0111 == 0o0111 {
|
||||
cgi::cgi(con, srv, path, url).await?;
|
||||
return Ok(());
|
||||
} else {
|
||||
logger::logger(con.peer_addr, Status::CGIError, &request);
|
||||
con.send_status(Status::CGIError, None).await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
|
||||
cgi::cgi(con, srv, path, url).await?;
|
||||
return Ok(());
|
||||
}
|
||||
},
|
||||
}
|
||||
if handle_cgi(&mut con, srv, &request, &url, &path).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
|
||||
logger::logger(con.peer_addr, Status::NotFound, &request);
|
||||
con.send_status(Status::NotFound, None).await?;
|
||||
|
||||
Reference in New Issue
Block a user