use std::io::{Write, BufReader, BufRead}; use std::thread; use std::net::{TcpListener, TcpStream}; use std::fs; use std::string::{String}; use std::process::Command; use packeteer::http1::*; use packeteer::{generate_kvheader, unwrap_url_into_segments}; struct Resource { contents: String, status_code: i32, mime: String, } fn process_cgi(filename: String) -> String { // This is gonna be the boggiest implementation of CGI that anyone // has ever seen in the history of the fucking world let query; let script; if filename.contains("?") { let vars = filename.to_string().find("?").unwrap(); let vars1 = vars + 1; query = &filename[vars1..]; script = &filename[..vars]; } else { query = ""; script = &filename; } println!("{}", script); let output; if query != "" { output = Command::new(format!("./{}", script)) .env("GATEWAY_INTERFACE", "CGI/1.1") .env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION"))) .env("REQUEST_METHOD", "GET") .env("QUERY_STRING", query) .env("SCRIPT_NAME", script) .output() .expect("failed to execute process"); } else { output = Command::new(format!("./{}", script)) .env("GATEWAY_INTERFACE", "CGI/1.1") .env("SERVER_SOFTWARE", format!("herb/{}", env!("CARGO_PKG_VERSION"))) .env("REQUEST_METHOD", "GET") .env("SCRIPT_NAME", script) .output() .expect("failed to execute process"); } let outputd = String::from_utf8_lossy(&output.stdout).to_string(); return outputd; } fn detect_media_type(filename: String) -> String { // The Lynx terminal browser made me do this. let path = filename.to_string(); let paths = check_if_path_exists(path); let path = filename.to_string(); let dir = check_if_dir(path); if paths == true && dir == false && filename.contains(".") == false { return "text/html".to_string(); } else { let ext_index = filename.to_string().find(".").unwrap(); let test = ext_index + 1; let ext = &filename[test..]; if filename[test..].contains("cgi") { return "text/html".to_string(); } match ext { "aac" => "audio/aac".to_string(), "avi" => "video/x-msvideo".to_string(), "bmp" => "image/bmp".to_string(), "bz2" => "application/x-bzip2".to_string(), "css" => "text/css".to_string(), "gz" => "application/gzip".to_string(), "gif" => "image/gif".to_string(), "png" => "image/png".to_string(), "pdf" => "application/pdf".to_string(), "jpeg" => "image/jpeg".to_string(), "jpg" => "image/jpeg".to_string(), "js" => "text/javascript".to_string(), "mid" => "audio/midi".to_string(), "midi" => "audio/midi".to_string(), "mp3" => "audio/mpeg".to_string(), "mp4" => "video/mp4".to_string(), "mpeg" => "video/mpeg".to_string(), "ogg" => "audio/ogg".to_string(), "oga" => "audio/ogg".to_string(), "ogv" => "video/ogg".to_string(), "opus" => "audio/opus".to_string(), "txt" => "text/plain".to_string(), "html" => "text/html".to_string(), "htm" => "text/html".to_string(), "cgi" => "text/html".to_string(), ".error_server_404" => "text/html".to_string(), ".error_server_501" => "text/html".to_string(), _ => "application/octet-stream".to_string(), } } } fn check_if_path_exists(path: String) -> bool { // This is probably not the best way of checking if a path // exists but I don't care :) let file = fs::metadata(path); match file { Ok(_) => true, Err(_) => false, } } fn check_if_dir(directory: String) -> bool { let path = directory + "/index.html"; let result = check_if_path_exists(path); return result; } fn generate_index(directory: String) -> String { let mut index = format!("

Directory of {}


Parent Directory
", directory, directory); for file in fs::read_dir(directory).unwrap() { index = format!("{}{}
", index, format!("\"/{}\"", file.as_ref().unwrap().path().display().to_string()), file.unwrap().file_name().into_string().unwrap_or("!!".to_string())); } return format!("{}
Generated by herb {}", index, env!("CARGO_PKG_VERSION")).to_string(); } fn get_page(filename: String) -> String { // The loaded page should be left immutable as it does // not need to be modified by the server. let path = filename.to_string(); let checks = check_if_path_exists(path.clone()); let index = check_if_dir(path.clone()); println!("{} {} {}", path, checks, index); if checks == true && index == false { if path.contains(".") != true && path.contains(".cgi?") != true { let result = generate_index(filename); return result.to_string(); } else if path.contains(".cgi?") { let result = process_cgi(filename); return result.to_string(); } } if filename.contains(".cgi") { let result = process_cgi(filename); return result.to_string(); } else { let result = fs::read_to_string(filename); match result { Ok(i) => i.to_string(), Err(_e) => "

500 Internal Server Error

The resource you are trying to access cannot be read by the server.

".to_string(), } } } fn grab_time() -> String{ let output = Command::new("date") .arg("+'%a, %d %b %Y %H:%m:%S %Z'") .output() .expect("failed to execute process"); return String::from_utf8_lossy(&output.stdout).to_string().replace("'","").replace("\n",""); } fn process_request(request: Vec) -> Resource { let input = String::from_utf8_lossy(&request).to_string(); let request = construct_request(&input); let mut path: String; let mut index = String::new(); let output; if &request.method == "GET" { println!("Stream sent GET request."); if request.location.segments.len() != 0 { path = unwrap_url_into_segments(request.location); path.remove(0); let exists = check_if_path_exists(path.clone()); if exists == false && path.contains(".cgi?") == false { let resource = Resource { contents: "

404 Not Found

The resource you are trying to locate cannot be accessed!

".to_string(), status_code: 404, mime: "text/html".to_string() }; return resource; } else { let dir = check_if_dir(path.clone()); if dir == true { index += &path; index += "/index.html"; output = index.as_str().clone(); } else { output = path.as_str().clone(); } } } else { output = "index.html"; } } else { let resource = Resource { contents: "

501 Not Implemented

The request sent by your web browser cannot be handled by this server software.

".to_string(), status_code: 501, mime: "text/html".to_string() }; println!("Stream sent unhandlable request."); return resource; } // Did you want to see chars.as_str().to_string()? let mut resource = Resource { contents: get_page(output.to_string()), status_code: 200, mime: detect_media_type(output.to_string()) }; if resource.contents.contains("500 Internal Server Error") { resource.status_code = 500; resource.mime = "text/html".to_string(); } return resource; } fn serve(mut stream: TcpStream) { thread::spawn(move || { println!("Stream thread created."); let mut request = Vec::new(); let mut reader = BufReader::new(&mut stream); reader .read_until(b'\n', &mut request) .expect("Failed to read from stream!"); let resource = process_request(request); let mut response_constructed = generate_response(resource.status_code, &resource.contents); response_constructed.headers.push(generate_kvheader("Content-Type", &resource.mime)); response_constructed.headers.push(generate_kvheader("Server", &format!("herb/{}", env!("CARGO_PKG_VERSION")))); if cfg!(unix) { response_constructed.headers.push(generate_kvheader("Date", &grab_time())) } stream.write(unpack_response(response_constructed).as_bytes()).unwrap(); stream.flush().unwrap(); }); } fn main() -> std::io::Result<()> { let listen = TcpListener::bind("0.0.0.0:8080")?; for stream in listen.incoming() { println!("Serving incoming stream."); serve(stream?); } Ok(()) }