use std::io::{Write, BufReader, BufRead, Read}; use std::thread; use std::net::{TcpListener, TcpStream}; use std::fs; use std::fs::File; use std::string::{String}; use std::process::Command; use packeteer::http1::*; use frostwalker; use packeteer::{generate_kvheader, unwrap_url_into_segments}; struct Resource { contents: Vec, status_code: i32, mime: String, iscgi: bool, } struct GetPageResult { is500: bool, is502: bool, is403: bool, contents: Vec, iscgi: bool, } #[derive(Clone)] struct Settings { cgi: bool, index_generation: bool, address: String, logging: bool, } impl Settings { fn new() -> Settings { return Settings { cgi: true, index_generation: true, address: "0.0.0.0:8080".to_string(), logging: false}; } } fn process_cgi(filename: String) -> Option> { // This is gonna be the bodgiest 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; } 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(); } 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(); } if output.is_ok() { return Some(output.unwrap().stdout); } else { return None; } } fn process_cgi_with_path(filename: String, segments: Vec, location: i32) -> Option> { let mut query: String = "".to_string(); let script = filename; let mut i = 0; let mut path = "".to_string(); for seg in segments { if !(i <= location) && !(seg.contains("?")) { if path == "".to_string() { path = format!("/{}", seg); } else { path = format!("{}/{}", path, seg); } } if !(i <= location) && seg.contains("?") { let split: Vec<&str> = seg.as_str().split("?").collect(); query = split[split.len()-1].to_string(); path = format!("{}/{}", path, split[0].to_string()); } i = i + 1; } path = format!("{}/", path); let output; if query != "".to_string() { 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("PATH_INFO", path) .env("SCRIPT_NAME", script) .output(); } 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("PATH_INFO", path) .env("SCRIPT_NAME", script) .output(); } if output.is_ok() { return Some(output.unwrap().stdout); } else { return None; } } 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.as_str().split(".").collect::>(); let ext = ext_index[ext_index.len()-1]; 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(), _ => "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, settings: Settings) -> GetPageResult { // 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()); let mut resultstruct = GetPageResult { is500: false, is502: false, is403: false, contents: vec![], iscgi: false }; if settings.logging { println!("{} {} {}", path, checks, index); } if checks == true && index == false { if path.contains(".") != true && path.contains(".cgi?") != true { let result; if settings.index_generation == false { result = "

403 Forbidden

You do not have permission to access this resource

".to_string(); resultstruct.is403 = true; } else { result = generate_index(filename); } resultstruct.contents = result.to_string().into_bytes(); return resultstruct; } else if path.contains(".cgi?") { if settings.cgi == false { let result = "

403 Forbidden

You do not have permission to access this resource

".to_string(); resultstruct.contents = result.into_bytes(); resultstruct.is403 = true; return resultstruct; } let result = process_cgi(filename); if result.is_some() { resultstruct.contents = result.unwrap(); resultstruct.iscgi = true; return resultstruct; } else { resultstruct.contents = "

500 Internal Server Error

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

".to_string().into_bytes(); resultstruct.is500 = true; return resultstruct; } } } if filename.contains(".cgi") { if settings.cgi == false { let result = "

403 Forbidden

You do not have permission to access this resource

".to_string(); resultstruct.contents = result.into_bytes(); resultstruct.is403 = true; return resultstruct; } let result = process_cgi(filename); if result.is_some() { resultstruct.contents = result.unwrap(); resultstruct.iscgi = true; return resultstruct; } else { resultstruct.contents = "

502 Bad Gateway

The server is unable to complete your request due to an error.

".to_string().into_bytes(); resultstruct.is502 = true; return resultstruct; } } else { let f = File::open(filename.clone()); if f.is_ok() { let mut buf: Vec = vec![]; let result = f.unwrap().read_to_end(&mut buf); if result.is_ok() { resultstruct.contents = buf; return resultstruct; } else { resultstruct.contents = "

502 Bad Gateway

The server is unable to complete your request due to an error.

".to_string().into_bytes(); resultstruct.is502 = true; return resultstruct; } } else { if filename == "index.html" { let newresult; if settings.index_generation == false { newresult = "

403 Forbidden

You do not have permission to access this resource

".to_string(); resultstruct.is403 = true; } else { newresult = generate_index(".".to_string()); } resultstruct.contents = newresult.to_string().into_bytes(); return resultstruct; } resultstruct.contents = "

500 Internal Server Error

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

".to_string().into_bytes(); resultstruct.is500 = true; return resultstruct; } } } 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, settings: Settings) -> Resource { let input = String::from_utf8_lossy(&request).to_string(); let prerequest = construct_request(&input); let request; if prerequest.is_some() { request = prerequest.unwrap(); } else { let resource = Resource { contents: "

400 Bad Request

The request you sent appears to be malformed.

".to_string().into_bytes(), status_code: 400, mime: "text/html".to_string(), iscgi: false }; return resource; } let mut path: String; let mut index = String::new(); let output; if &request.method == "GET" { if settings.logging { println!("Stream sent GET request."); } if request.location.segments.len() != 0 { let segclone = request.location.segments.clone(); path = unwrap_url_into_segments(request.location); if path.contains(".cgi/") { let mut cgiscript = 0; let mut i = 0; let mut cgipath: Vec = vec![]; for segment in &segclone { if segment.contains(".cgi") { cgipath.push(segment.to_string()); cgiscript = i; break; } else { cgipath.push(segment.to_string()); i = i + 1; } } let mut cgipathraw = "".to_string(); for seg in cgipath { if cgipathraw == "".to_string() { cgipathraw = seg; } else { cgipathraw = format!("{}/{}", cgipathraw, seg); } } if settings.cgi == false { let newcontents = "

403 Forbidden

You do not have permission to access this resource

".to_string().into_bytes(); let resource = Resource { contents: newcontents, status_code: 403, mime: "text/html".to_string(), iscgi: false }; return resource; } let contents = process_cgi_with_path(cgipathraw, segclone, cgiscript); if contents.is_some() { let resource = Resource { contents: contents.unwrap(), status_code: 200, mime: "text/html".to_string(), iscgi: true }; return resource; } else { let newcontents = "

502 Bad Gateway

The server is unable to complete your request due to an error.

".to_string().into_bytes(); let resource = Resource { contents: newcontents, status_code: 502, mime: "text/html".to_string(), iscgi: false }; return resource; } } 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().into_bytes(), status_code: 404, mime: "text/html".to_string(), iscgi: false }; return resource; } else { let dir = check_if_dir(path.clone()); if dir == true { index += &path; index += "/index.html"; output = index.as_str(); } else { output = path.as_str(); } } } 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().into_bytes(), status_code: 501, mime: "text/html".to_string(), iscgi: false }; if settings.logging { println!("Stream sent unhandlable request."); } return resource; } // Did you want to see chars.as_str().to_string()? let rescontents = get_page(output.to_string(), settings.clone()); let mut resource = Resource { contents: rescontents.contents, status_code: 200, mime: detect_media_type(output.split("?").collect::>()[0].to_string()), iscgi: false }; if rescontents.is500 { resource.status_code = 500; resource.mime = "text/html".to_string(); } if rescontents.is502 { resource.status_code = 502; resource.mime = "text/html".to_string(); } if rescontents.is403 { resource.status_code = 403; resource.mime = "text/html".to_string(); } if rescontents.iscgi { resource.iscgi = true; } return resource; } fn serve(mut stream: TcpStream, settings: Settings) { thread::spawn(move || { if settings.logging { 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, settings.clone()); let mut response_constructed = generate_response(resource.status_code, ""); 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())) } if resource.iscgi { let header = "HTTP/1.1 200 OK\r\n".to_string().into_bytes(); stream.write(header.as_slice()).unwrap(); stream.write(&resource.contents).unwrap(); stream.flush().unwrap(); return; } let mut contents_clone = resource.contents.clone(); let mut unpacked_response = unpack_response(response_constructed).into_bytes(); unpacked_response.append(&mut contents_clone); stream.write(unpacked_response.as_slice()).unwrap(); stream.flush().unwrap(); }); } fn process_settings() -> Settings { let string = fs::read_to_string("../settings.toml"); if string.is_err() { return Settings::new(); } let parsed = frostwalker::parse(&string.unwrap()); if parsed.is_err() { return Settings::new(); } let hashmap = parsed.unwrap(); let mut settings = Settings::new(); if hashmap.get("cgi").is_some() { if hashmap.get("cgi").unwrap_or(&"".to_string()).parse::().is_ok() { settings.cgi = hashmap.get("cgi").unwrap_or(&"true".to_string()).parse::().unwrap_or(true); } } if hashmap.get("index_generation").is_some() { if hashmap.get("index_generation").unwrap_or(&"".to_string()).parse::().is_ok() { settings.index_generation = hashmap.get("index_generation").unwrap_or(&"true".to_string()).parse::().unwrap_or(true); } } if hashmap.get("logging").is_some() { if hashmap.get("logging").unwrap_or(&"".to_string()).parse::().is_ok() { settings.logging = hashmap.get("logging").unwrap_or(&"false".to_string()).parse::().unwrap_or(true); } } if hashmap.get("address").is_some() { settings.address = hashmap.get("address").unwrap_or(&"0.0.0.0:8080".to_string()).to_string(); } return settings; } fn main() -> std::io::Result<()> { let settings = process_settings(); let listen = TcpListener::bind(settings.address.clone())?; for stream in listen.incoming() { if settings.logging { println!("Serving incoming stream."); } serve(stream?, settings.clone()); } Ok(()) }