~int80h/gemserv

4fb31bec38be0cc0cf0780744c6f1b4036b1ff3f — int 80h 2 months ago e5c44c2
Small amount of refactoring
6 files changed, 80 insertions(+), 72 deletions(-)

M Cargo.lock
M Cargo.toml
M src/cgi.rs
M src/config.rs
M src/conn.rs
M src/main.rs
M Cargo.lock => Cargo.lock +9 -9
@@ 140,7 140,7 @@ dependencies = [

[[package]]
name = "gemserv"
version = "0.4.7"
version = "0.4.8"
dependencies = [
 "futures-util",
 "log",


@@ 193,9 193,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "libc"
version = "0.2.107"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"

[[package]]
name = "lock_api"


@@ 326,9 326,9 @@ dependencies = [

[[package]]
name = "openssl-sys"
version = "0.9.70"
version = "0.9.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6517987b3f8226b5da3661dad65ff7f300cc59fb5ea8333ca191fc65fde3edf"
checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73"
dependencies = [
 "autocfg",
 "cc",


@@ 521,9 521,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"

[[package]]
name = "tokio"
version = "1.13.0"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee"
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
dependencies = [
 "autocfg",
 "bytes",


@@ 541,9 541,9 @@ dependencies = [

[[package]]
name = "tokio-macros"
version = "1.5.1"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095"
checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
dependencies = [
 "proc-macro2",
 "quote",

M Cargo.toml => Cargo.toml +2 -2
@@ 1,6 1,6 @@
[package]
name = "gemserv"
version = "0.4.7"
version = "0.4.8"
authors = ["int 80h <int@80h.dev>"]
edition = "2018"
description = "A gemini server"


@@ 12,7 12,7 @@ license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
tokio = { version = "1.13.0", features = [ "full" ] }
tokio = { version = "1.14", features = [ "full" ] }
openssl = "0.10.38"
tokio-openssl = "0.6.3"
futures-util = "0.3.17"

M src/cgi.rs => src/cgi.rs +3 -4
@@ 89,7 89,6 @@ fn check(byt: u8, peer_addr: SocketAddr, u: &url::Url) -> bool {
#[cfg(feature = "cgi")]
pub async fn cgi(
    con: &mut conn::Connection,
    srv: &config::ServerCfg,
    path: PathBuf,
    url: &url::Url,
    script_name: String,


@@ 97,7 96,7 @@ pub async fn cgi(
) -> Result<(), io::Error> {

    let x509 = con.stream.ssl().peer_certificate();
    let mut envs = envs(con.peer_addr, x509, srv, &url);
    let mut envs = envs(con.peer_addr, x509, &con.srv, &url);
    envs.insert("SCRIPT_NAME".into(), script_name);
    envs.insert("PATH_INFO".into(), path_info);



@@ 148,7 147,7 @@ pub async fn cgi(
}

#[cfg(feature = "scgi")]
pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection, srv: &config::ServerCfg) -> Result<(), io::Error> {
pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection) -> Result<(), io::Error> {
    let addr = addr
        .to_socket_addrs()?
        .next()


@@ 163,7 162,7 @@ pub async fn scgi(addr: String, u: url::Url, mut con: conn::Connection, srv: &co
        }
    };
    let x509 = con.stream.ssl().peer_certificate();
    let envs = envs(con.peer_addr, x509, srv, &u);
    let envs = envs(con.peer_addr, x509, &con.srv, &u);
    let len = 0usize;
    let mut byt = String::from(format!("CONTENT_LENGTH\x00{}\x00SCGI\x001\x00
        RQUEST_METHOD\x00POST\x00REQUEST_URI\x00{}\x00", len, u.path()));

M src/config.rs => src/config.rs +1 -1
@@ 64,4 64,4 @@ impl Config {
        }
        map
    }
}
}
\ No newline at end of file

M src/conn.rs => src/conn.rs +1 -0
@@ 12,6 12,7 @@ use crate::status::Status;
pub struct Connection {
    pub stream: SslStream<TcpStream>,
    pub peer_addr: SocketAddr,
    pub srv: crate::config::ServerCfg,
}

impl Connection {

M src/main.rs => src/main.rs +64 -56
@@ 64,7 64,7 @@ async fn get_binary(mut con: conn::Connection, path: PathBuf, meta: String) -> i
    Ok(())
}

async fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
async fn get_content(path: PathBuf, u: &url::Url) -> Result<String, io::Error> {
    let meta = tokio::fs::metadata(&path).await?;
    if meta.is_file() {
        return Ok(tokio::fs::read_to_string(path).await?);


@@ 119,12 119,11 @@ async fn get_content(path: PathBuf, u: url::Url) -> Result<String, io::Error> {
#[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) {
    if con.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();


@@ 143,11 142,11 @@ async fn handle_cgi(
        let meta = tokio::fs::metadata(&path).await?;
        let perm = meta.permissions();

        match &srv.server.cgipath {
        match &con.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?;
                    cgi::cgi(con, path, url, script_name, path_info).await?;
                    return Ok(true);
                } else {
                    logger::logger(con.peer_addr, Status::CGIError, request);


@@ 158,7 157,7 @@ async fn handle_cgi(
            },
            None => {
                if meta.is_file() && perm.mode() & 0o0111 == 0o0111 {
                    cgi::cgi(con, srv, path, url, script_name, path_info).await?;
                    cgi::cgi(con, path, url, script_name, path_info).await?;
                    return Ok(true);
                }
            },


@@ 167,30 166,22 @@ async fn handle_cgi(
    Ok(false)
}

// TODO Rewrite this monster.
async fn handle_connection(
    mut con: conn::Connection,
    srv: &config::ServerCfg,
) -> Result<(), io::Error> {
    let index = match &srv.server.index {
        Some(i) => i.clone(),
        None => "index.gemini".to_string(),
    };
async fn get_request(mut con: conn::Connection) -> Result<(conn::Connection, url::Url), String> {
    let mut buffer = [0; 1024];
    let len = match tokio::time::timeout(tokio::time::Duration::from_secs(5), con.stream.read(&mut buffer)).await {
        Ok(result) => result.unwrap(),
        Err(_) => {
        Err(e) => {
            logger::logger(con.peer_addr, Status::BadRequest, "");
            con.send_status(Status::BadRequest, None).await?;
            return Ok(());
            con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
            return Err(e.to_string());
        }
    };
    let mut request = match String::from_utf8(buffer[..len].to_vec()) {
        Ok(request) => request,
        Err(_) => {
        Err(e) => {
            logger::logger(con.peer_addr, Status::BadRequest, "");
            con.send_status(Status::BadRequest, None).await?;
            return Ok(());
            con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
            return Err(e.to_string());
        }
    };
    if request.starts_with("//") {


@@ 208,35 199,48 @@ async fn handle_connection(
        Ok(url) => url,
        Err(_) => {
            logger::logger(con.peer_addr, Status::BadRequest, &request);
            con.send_status(Status::BadRequest, None).await?;
            return Ok(());
            con.send_status(Status::BadRequest, None).await.map_err(|e| e.to_string())?;
            return Err("Bad url".to_string());
        }
    };

    if Some(srv.server.hostname.as_str()) != url.host_str() {
        logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
        con.send_status(Status::ProxyRequestRefused, None).await?;
        return Ok(());
    if Some(con.srv.server.hostname.as_str()) != url.host_str() {
        logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
        con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
        return Err("Wrong host".to_string());
    }

    match url.port() {
        Some(p) => {
            if p != srv.port {
                logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
            if p != con.srv.port {
                logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
                con.send_status(status::Status::ProxyRequestRefused, None)
                    .await?;
                    .await.map_err(|e| e.to_string())?;
            }
        }
        None => {}
    }

    if url.scheme() != "gemini" {
        logger::logger(con.peer_addr, Status::ProxyRequestRefused, &request);
        con.send_status(Status::ProxyRequestRefused, None).await?;
        return Ok(());
        logger::logger(con.peer_addr, Status::ProxyRequestRefused, &url.as_str());
        con.send_status(Status::ProxyRequestRefused, None).await.map_err(|e| e.to_string())?;
        return Err("scheme not gemini".to_string());
    }
    
    return Ok((con, url))
}

    match &srv.server.redirect {
// TODO Rewrite this monster.
async fn handle_connection(
    mut con: conn::Connection,
    url: url::Url
) -> Result<(), io::Error> {
    let index = match &con.srv.server.index {
        Some(i) => i.clone(),
        None => "index.gemini".to_string(),
    };

    match &con.srv.server.redirect.to_owned() {
        Some(re) => {
            let u = match url.path() {
                "/" => "/",


@@ 244,7 248,7 @@ async fn handle_connection(
            };
            match re.get(u) {
                Some(r) => {
                    logger::logger(con.peer_addr, Status::RedirectTemporary, &request);
                    logger::logger(con.peer_addr, Status::RedirectTemporary, &url.as_str());
                    con.send_status(Status::RedirectTemporary, Some(r)).await?;
                    return Ok(());
                }


@@ 255,7 259,7 @@ async fn handle_connection(
    }

    #[cfg(feature = "proxy")]
    if let Some(pr) = &srv.server.proxy_all {
    if let Some(pr) = con.srv.server.proxy_all.to_owned() {
        let host_port: Vec<&str> = pr.splitn(2, ':').collect();
        let host = host_port[0];
        let port: Option<u16>;


@@ 269,12 273,12 @@ async fn handle_connection(
        upstream_url.set_host(Some(host)).unwrap();
        upstream_url.set_port(port).unwrap();

        revproxy::proxy_all(pr, upstream_url, con).await?;
        revproxy::proxy_all(pr.as_str(), upstream_url, con).await?;
        return Ok(());
    }

    #[cfg(feature = "proxy")]
    match &srv.server.proxy {
    match &con.srv.server.proxy {
        Some(pr) => match url.path_segments().map(|c| c.collect::<Vec<_>>()) {
            Some(s) => match pr.get(s[0]) {
                Some(p) => {


@@ 289,7 293,7 @@ async fn handle_connection(
    }

    #[cfg(feature = "scgi")]
    match &srv.server.scgi {
    match &con.srv.server.scgi {
        Some(sc) => {
            let u = match url.path() {
                "/" => "/",


@@ 297,7 301,7 @@ async fn handle_connection(
            };
        match sc.get(u) {
            Some(r) => {
                cgi::scgi(r.to_string(), url, con, srv).await?;
                cgi::scgi(r.to_string(), url, con).await?;
                return Ok(());
            }
            None => {}


@@ 309,7 313,7 @@ async fn handle_connection(

    let mut path = PathBuf::new();

    if url.path().starts_with("/~") && srv.server.usrdir.unwrap_or(false) {
    if url.path().starts_with("/~") && con.srv.server.usrdir.unwrap_or(false) {
        let usr = url.path().trim_start_matches("/~");
        let usr: Vec<&str> = usr.splitn(2, "/").collect();
        if cfg!(target_os = "macos") {


@@ 323,7 327,7 @@ async fn handle_connection(
            path.push(format!("{}/{}/", usr[0], "public_gemini"));
        }
    } else {
        path.push(&srv.server.dir);
        path.push(&con.srv.server.dir);
        if url.path() != "" || url.path() != "/" {
            let decoded = util::url_decode(url.path().trim_start_matches("/").as_bytes());
            path.push(decoded);


@@ 333,11 337,11 @@ 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? {
        if handle_cgi(&mut con, &url.as_str(), &url, &path).await? {
            return Ok(());
        }

        logger::logger(con.peer_addr, Status::NotFound, &request);
        logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
        con.send_status(Status::NotFound, None).await?;
        return Ok(());
    }


@@ 349,7 353,7 @@ async fn handle_connection(
    // This block is terrible
    if meta.is_dir() {
        if !url.path().ends_with("/") {
            logger::logger(con.peer_addr, Status::RedirectPermanent, &request);
            logger::logger(con.peer_addr, Status::RedirectPermanent, &url.as_str());
            con.send_status(
                Status::RedirectPermanent,
                Some(format!("{}/", url).as_str()),


@@ 372,35 376,35 @@ async fn handle_connection(
    }

    #[cfg(feature = "cgi")]
    if handle_cgi(&mut con, srv, &request, &url, &path).await? {
    if handle_cgi(&mut con, &url.as_str(), &url, &path).await? {
        return Ok(());
    }

    if meta.is_file() && perm.mode() & 0o0111 == 0o0111  {
        logger::logger(con.peer_addr, Status::NotFound, &request);
        logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
        con.send_status(Status::NotFound, None).await?;
        return Ok(());
    }

    if perm.mode() & 0o0444 != 0o0444  {
        logger::logger(con.peer_addr, Status::NotFound, &request);
        logger::logger(con.peer_addr, Status::NotFound, &url.as_str());
        con.send_status(Status::NotFound, None).await?;
        return Ok(());
    }

    let mut mime = get_mime(&path);
    if mime == "text/gemini" && srv.server.lang.is_some() {
        mime += &("; lang=".to_string() + &srv.server.lang.to_owned().unwrap());
    if mime == "text/gemini" && con.srv.server.lang.is_some() {
        mime += &("; lang=".to_string() + &con.srv.server.lang.to_owned().unwrap());
    }
    if !mime.starts_with("text/") {
        logger::logger(con.peer_addr, Status::Success, &request);
        logger::logger(con.peer_addr, Status::Success, &url.as_str());
        get_binary(con, path, mime).await?;
        return Ok(());
    }
    let content = get_content(path, url).await?;
    let content = get_content(path, &url).await?;
    con.send_body(status::Status::Success, Some(&mime), Some(content))
        .await?;
    logger::logger(con.peer_addr, Status::Success, &request);
    logger::logger(con.peer_addr, Status::Success, &url.as_str());

    Ok(())
}


@@ 484,10 488,14 @@ fn main() -> io::Result<()> {
                        None => cmap.get(&default).unwrap(),
                    },
                    None => cmap.get(&default).unwrap(),
                };
                }.to_owned();

                let con = conn::Connection { stream, peer_addr };
                handle_connection(con, srv).await?;
                let con = conn::Connection { stream, peer_addr, srv };
                let (con, url) = match get_request(con).await {
                    Ok((c, u)) => (c, u),
                    Err(_) => return Ok(()) as io::Result<()>,
                };
                handle_connection(con, url).await?;

                Ok(()) as io::Result<()>
            };