herb/src/main.rs

295 lines
9.1 KiB
Rust
Raw Normal View History

use std::io::{Write, BufReader, BufRead, Read};
2021-12-24 21:30:39 +00:00
use std::thread;
2021-08-29 16:07:05 +01:00
use std::net::{TcpListener, TcpStream};
2021-08-30 16:17:11 +01:00
use std::fs;
use std::fs::File;
2021-08-30 16:17:11 +01:00
use std::string::{String};
use std::process::Command;
use packeteer::http1::*;
use packeteer::{generate_kvheader, unwrap_url_into_segments};
struct Resource {
contents: Vec<u8>,
status_code: i32,
mime: String,
}
2021-08-30 16:17:11 +01:00
struct GetPageResult {
is500: bool,
contents: Vec<u8>,
}
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;
}
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 {
2024-01-15 10:20:23 +00:00
let ext_index = filename.as_str().split(".").collect::<Vec<&str>>();
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(),
"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;
}
2021-09-05 16:16:07 +01:00
fn generate_index(directory: String) -> String {
let mut index = format!("<!DOCTYPE HTML><html><body><h1>Directory of {}</h1><hr/><a href=\"/{}/../\">Parent Directory</a><br/>", directory, directory);
2021-09-05 16:16:07 +01:00
for file in fs::read_dir(directory).unwrap() {
index = format!("{}<a href={}>{}</a><br/>", index, format!("\"/{}\"", file.as_ref().unwrap().path().display().to_string()), file.unwrap().file_name().into_string().unwrap_or("!!".to_string()));
2021-09-05 16:16:07 +01:00
}
2024-01-15 10:20:23 +00:00
return format!("{}<hr/><i>Generated by herb/{}</i>", index, env!("CARGO_PKG_VERSION")).to_string();
2021-09-05 16:16:07 +01:00
}
fn get_page(filename: String) -> GetPageResult {
2021-08-30 16:17:11 +01:00
// The loaded page should be left immutable as it does
// not need to be modified by the server.
2021-09-05 16:16:07 +01:00
let path = filename.to_string();
let checks = check_if_path_exists(path.clone());
let index = check_if_dir(path.clone());
2021-09-05 16:16:07 +01:00
let mut resultstruct = GetPageResult { is500: false, contents: vec![] };
2021-09-05 16:16:07 +01:00
println!("{} {} {}", path, checks, index);
if checks == true && index == false {
if path.contains(".") != true && path.contains(".cgi?") != true {
2021-09-05 16:16:07 +01:00
let result = generate_index(filename);
resultstruct.contents = result.to_string().into_bytes();
return resultstruct;
} else if path.contains(".cgi?") {
let result = process_cgi(filename);
resultstruct.contents = result.to_string().into_bytes();
return resultstruct;
2021-09-05 16:16:07 +01:00
}
}
if filename.contains(".cgi") {
let result = process_cgi(filename);
resultstruct.contents = result.to_string().into_bytes();
return resultstruct;
} else {
2024-01-15 10:39:14 +00:00
let f = File::open(filename.clone());
if f.is_ok() {
let mut buf: Vec<u8> = vec![];
let result = f.unwrap().read_to_end(&mut buf);
if result.is_ok() {
resultstruct.contents = buf;
return resultstruct;
} else {
resultstruct.contents = "<!DOCTYPE HTML><html><body><h1>500 Internal Server Error</h1><p>The resource you are trying to access cannot be read by the server.</p></body></html>".to_string().into_bytes();
resultstruct.is500 = true;
return resultstruct;
}
} else {
2024-01-15 10:39:14 +00:00
if filename == "index.html" {
let newresult = generate_index(".".to_string());
resultstruct.contents = newresult.to_string().into_bytes();
return resultstruct;
}
resultstruct.contents = "<!DOCTYPE HTML><html><body><h1>500 Internal Server Error</h1><p>The resource you are trying to access cannot be read by the server.</p></body></html>".to_string().into_bytes();
resultstruct.is500 = true;
return resultstruct;
}
}
}
2021-08-29 16:07:05 +01:00
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<u8>) -> 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: "<!DOCTYPE HTML><html><body><h1>404 Not Found</h1><p>The resource you are trying to locate cannot be accessed!</p></body></html>".to_string().into_bytes(), 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: "<!DOCTYPE HTML><html><body><h1>501 Not Implemented</h1><p>The request sent by your web browser cannot be handled by this server software.</p></body></html>".to_string().into_bytes(), status_code: 501, mime: "text/html".to_string() };
println!("Stream sent unhandlable request.");
return resource;
}
2021-09-02 16:25:13 +01:00
// Did you want to see chars.as_str().to_string()?
let rescontents = get_page(output.to_string());
2024-01-15 10:20:23 +00:00
let mut resource = Resource { contents: rescontents.contents, status_code: 200, mime: detect_media_type(output.split("?").collect::<Vec<&str>>()[0].to_string()) };
if rescontents.is500 {
resource.status_code = 500;
resource.mime = "text/html".to_string();
}
return resource;
2021-08-30 17:18:22 +01:00
}
2021-08-29 16:07:05 +01:00
fn serve(mut stream: TcpStream) {
2021-12-24 21:30:39 +00:00
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, "");
response_constructed.headers.push(generate_kvheader("Content-Type", &resource.mime));
response_constructed.headers.push(generate_kvheader("Server", &format!("herb/{}", env!("CARGO_PKG_VERSION"))));
2021-12-24 21:30:39 +00:00
if cfg!(unix) {
response_constructed.headers.push(generate_kvheader("Date", &grab_time()))
2021-12-24 21:30:39 +00:00
}
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();
2021-12-24 21:30:39 +00:00
stream.flush().unwrap();
});
2021-08-29 16:07:05 +01:00
}
fn main() -> std::io::Result<()> {
let listen = TcpListener::bind("0.0.0.0:8080")?;
2021-08-29 16:07:05 +01:00
for stream in listen.incoming() {
2021-08-30 17:18:22 +01:00
println!("Serving incoming stream.");
2021-08-29 16:07:05 +01:00
serve(stream?);
}
Ok(())
2021-08-29 15:18:20 +01:00
}