hyper_cgi 22.4.15

Run CGI scripts with hyper
Documentation
#[macro_use]
extern crate lazy_static;
use core::iter;
use core::str::from_utf8;
use rand::{distributions::Alphanumeric, thread_rng, Rng};

use futures::FutureExt;

use hyper::server::Server;

#[macro_export]
macro_rules! some_or {
    ($e:expr, $b:block) => {
        if let Some(x) = $e {
            x
        } else {
            $b
        }
    };
}

#[macro_export]
macro_rules! ok_or {
    ($e:expr, $b:block) => {
        if let Ok(x) = $e {
            x
        } else {
            $b
        }
    };
}

lazy_static! {
    static ref ARGS: clap::ArgMatches<'static> = parse_args();
}

pub struct ServerState {
    users: Vec<(String, String)>,
}

pub fn parse_auth(req: &hyper::Request<hyper::Body>) -> Option<(String, String)> {
    let line = some_or!(
        req.headers()
            .get("authorization")
            .and_then(|h| Some(h.as_bytes())),
        {
            return None;
        }
    );
    let u = ok_or!(String::from_utf8(line[6..].to_vec()), {
        return None;
    });
    let decoded = ok_or!(base64::decode(&u), {
        return None;
    });
    let s = ok_or!(String::from_utf8(decoded), {
        return None;
    });
    if let [username, password] = s.as_str().split(':').collect::<Vec<_>>().as_slice() {
        return Some((username.to_string(), password.to_string()));
    }
    return None;
}

fn auth_response(
    req: &hyper::Request<hyper::Body>,
    users: &Vec<(String, String)>,
) -> Option<hyper::Response<hyper::Body>> {
    if users.len() == 0 {
        return None;
    }

    let (rusername, rpassword) = match parse_auth(req) {
        Some(x) => x,
        None => {
            println!("no credentials in request");
            let builder = hyper::Response::builder()
                .header("WWW-Authenticate", "Basic realm=User Visible Realm")
                .status(hyper::StatusCode::UNAUTHORIZED);
            return Some(builder.body(hyper::Body::empty()).unwrap());
        }
    };

    for (username, password) in users {
        if rusername == *username && rpassword == *password {
            println!("CREDENTIALS OK {:?} {:?}", &rusername, &rpassword);
            return None;
        }
    }

    println!("ServerState: wrong user/pass");
    println!("user: {:?}", rusername);
    println!("pass: {:?}", rpassword);
    let builder = hyper::Response::builder()
        .header("WWW-Authenticate", "Basic realm=User Visible Realm")
        .status(hyper::StatusCode::UNAUTHORIZED);
    return Some(
        builder
            .body(hyper::Body::empty())
            .unwrap_or(hyper::Response::default()),
    );
}

async fn call(
    serv: std::sync::Arc<std::sync::Mutex<ServerState>>,
    req: hyper::Request<hyper::Body>,
) -> hyper::Response<hyper::Body> {
    println!("call {:?}", req.uri().path());

    let path = req.uri().path();

    if path == "/_noauth" {
        serv.lock().unwrap().users = vec![];
        return hyper::Response::default();
    }

    if path.starts_with("/_make_user/") {
        let builder = hyper::Response::builder();
        let username = path[12..].to_owned();
        let password = iter::repeat(())
            .map(|()| thread_rng().sample(Alphanumeric))
            .take(10)
            .collect::<Vec<u8>>();
        let mut password:String = from_utf8(&password).unwrap().to_string();

        for (u, p) in serv.lock().unwrap().users.iter() {
            if username == *u {
                password = p.clone();
                return builder.body(hyper::Body::from(password)).unwrap();
            }
        }
        serv.lock()
            .unwrap()
            .users
            .push((username, password.clone()));
        println!("users: {:?}", serv.lock().unwrap().users);
        return builder.body(hyper::Body::from(password)).unwrap();
    }

    if let Some(response) = auth_response(&req, &serv.lock().unwrap().users) {
        return response;
    }

    let workdir =
        std::path::PathBuf::from(ARGS.value_of("dir").expect("missing working directory"));

    let mut cmd = tokio::process::Command::new(ARGS.value_of("cmd").expect("missing cmd"));

    for arg in ARGS.values_of("args").unwrap() {
        cmd.arg(&arg);
    }
    cmd.current_dir(&workdir);
    cmd.env("PATH_INFO", path);

    hyper_cgi::do_cgi(req, cmd).await.0
}

#[tokio::main]
async fn main() {
    let server_state = std::sync::Arc::new(std::sync::Mutex::new(ServerState { users: vec![] }));

    let make_service = hyper::service::make_service_fn(move |_| {
        let server_state = server_state.clone();

        let service = hyper::service::service_fn(move |_req| {
            let server_state = server_state.clone();

            call(server_state, _req).map(Ok::<_, hyper::http::Error>)
        });

        futures::future::ok::<_, hyper::http::Error>(service)
    });

    let addr = format!(
        "0.0.0.0:{}",
        ARGS.value_of("port").unwrap_or("8000").to_owned()
    )
    .parse()
    .unwrap();
    let server = Server::bind(&addr).serve(make_service);
    println!("Now listening on {}", addr);

    if let Err(e) = server.await {
        eprintln!("server error: {}", e);
    }
}

fn parse_args() -> clap::ArgMatches<'static> {
    let args = {
        let mut args = vec![];
        for arg in std::env::args() {
            args.push(arg);
        }
        args
    };

    println!("ARGS {:?}", args);

    println!("args: {:?}", args);

    let app = clap::App::new("hyper-cgi-test-server")
        .arg(clap::Arg::with_name("dir").long("dir").takes_value(true))
        .arg(clap::Arg::with_name("cmd").long("cmd").takes_value(true))
        .arg(
            clap::Arg::with_name("args")
                .long("args")
                .short("a")
                .takes_value(true)
                .multiple(true),
        )
        .arg(clap::Arg::with_name("port").long("port").takes_value(true));

    app.get_matches_from(args)
}