diff --git a/core/http/Cargo.toml b/core/http/Cargo.toml index d2fc1338f98ce3949c2fc1087ee4d3e1a5aad8e9..5d24dbd889e2de9c7013b930d543cecd6cbf3aed 100644 --- a/core/http/Cargo.toml +++ b/core/http/Cargo.toml @@ -3,8 +3,12 @@ name = "http" version = "0.1.0" edition = "2021" +[features] +secure = [] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] tokio = { version = "1.28.2", features = ["full"] } phf = { version = "0.11", features = ["macros"] } diff --git a/core/http/src/handlers/handler.rs b/core/http/src/handlers/handler.rs index fa5f75a6942fe178681ec868ecdabe3b641a67fa..38315a1cea3bf365d89699c3315bb5686edcc2f7 100644 --- a/core/http/src/handlers/handler.rs +++ b/core/http/src/handlers/handler.rs @@ -1,10 +1,6 @@ use std::{io, path::PathBuf}; -use tokio::{ - io::{AsyncBufReadExt, AsyncReadExt, BufReader}, - net::TcpStream, -}; -use tokio_native_tls::{TlsStream, TlsAcceptor}; +use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead}; use crate::{handling::{ file_handlers::NamedFile, @@ -29,11 +25,7 @@ static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; /// /// # Panics /// No Panics -pub async fn handle_connection(stream: TcpStream, mountpoints: Vec<MountPoint<'_>>, acceptor: TlsAcceptor) { - let Ok(mut stream) = acceptor.accept(stream).await else { - eprintln!("\x1b[31mClient used http, not https\x1b[0m"); - return; - }; +pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint<'_>>) { let mut buf_reader = BufReader::new(&mut stream); let mut http_request: Vec<String> = Vec::with_capacity(10); loop { @@ -197,7 +189,7 @@ fn failure_handler(status: Status) -> Response { } /// Handler for len_not_defined errors. writes back directly -async fn len_not_defined(stream: TlsStream<TcpStream>, status: Status) -> io::Result<()> { +async fn len_not_defined<T: AsyncRead + AsyncWrite + std::marker::Unpin>(stream: T, status: Status) -> io::Result<()> { let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap(); Response { cookies: None, @@ -215,14 +207,3 @@ fn error_occured_when_writing(e: io::Error) { eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m"); } -// async fn redirect_to_secure(mut stream: TcpStream) { -// let resp = Response { -// cookies: None, -// status: Some(Status::MovedPermanently), -// headers: vec!["Location: https://127.0.0.1".to_string()], -// body: Box::new("") -// }.build(None); -// if let Err(e) = stream.write_all(&resp).await { -// error_occured_when_writing(e); -// } -// } diff --git a/core/http/src/handling/response/build_and_write.rs b/core/http/src/handling/response/build_and_write.rs index 3b6bd1b42a61717d06b86ccc28dbb6d51912495d..1d9e162fbda2c2e2f3d2ec07a2e55d83aa641f7d 100644 --- a/core/http/src/handling/response/build_and_write.rs +++ b/core/http/src/handling/response/build_and_write.rs @@ -1,7 +1,6 @@ use std::io::Result; -use tokio::{io::AsyncWriteExt, net::TcpStream}; -use tokio_native_tls::TlsStream; +use tokio::{io::{AsyncWriteExt, AsyncRead, AsyncWrite}, net::TcpStream}; use crate::handling::{methods::Method, request::Request, response::Status}; @@ -33,7 +32,7 @@ impl Response { compiled_out } /// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response] - pub async fn write(self, mut stream: TlsStream<TcpStream>, request: Option<Request>) -> Result<()> { + pub async fn write<T: AsyncRead + AsyncWrite + std::marker::Unpin>(self, mut stream: T, request: Option<Request>) -> Result<()> { let resp = self.build(request); stream.write_all(&resp).await?; Ok(()) diff --git a/core/http/src/setup.rs b/core/http/src/setup.rs index 4730fdb7fe2d8d4eb7d24fda3be30f75b606572b..f451ed4e8d0c82da01979d76d5c6298cd84935c7 100644 --- a/core/http/src/setup.rs +++ b/core/http/src/setup.rs @@ -3,8 +3,9 @@ use std::{thread::available_parallelism}; use tokio::{ net::TcpListener, select, - signal::unix::{signal, SignalKind}, + signal::unix::{signal, SignalKind, Signal}, }; + use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor}; use crate::{ @@ -29,7 +30,9 @@ pub struct Config { mountpoints: Option<Vec<MountPoint<'static>>>, /// Contains a [tokio::net::TcpListener] that is bound for the server address: TcpListener, + #[cfg(feature = "secure")] to_secure_redirect: TcpListener, + #[cfg(feature = "secure")] tls_acceptor: TlsAcceptor, } @@ -93,36 +96,69 @@ impl<'a> Config { /// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there /// is no interrupt signal pub async fn launch(self) { - println!( - "Server launched from https://{} and http://{}", - self.address.local_addr().unwrap(), self.to_secure_redirect.local_addr().unwrap() - ); + { + #[cfg(feature = "secure")] { + println!( + "Server launched from https://{} and http://{}", + self.address.local_addr().unwrap(), self.to_secure_redirect.local_addr().unwrap() + ); + } + #[cfg(not(feature = "secure"))] { + println!("Server launched from http://{}", self.address.local_addr().unwrap()) + } + } let mut sigint = signal(SignalKind::interrupt()).unwrap(); let location_string = format!("Location: https://{}", self.address.local_addr().unwrap()); loop { - select! { - _ = sigint.recv() => { - println!("Shutting down..."); - break; - } - Ok((socket, _)) = self.address.accept() => { - let mountpoints = self.mountpoints.clone().unwrap(); - let tls_acceptor = self.tls_acceptor.clone(); - tokio::spawn(async move { handle_connection(socket, mountpoints, tls_acceptor).await; }); - } - Ok((socket, _)) = self.to_secure_redirect.accept() => { - let redirect_response = Response { - headers: vec![location_string.clone()], - cookies: None, - status: Some(Status::MovedPermanently), - body: Box::new(""), - }; - tokio::spawn(async move { let _ = redirect_response.write_unencrypted(socket).await; }); - } + if !self.selector(&mut sigint, &location_string).await { + break; } } } + #[cfg(feature = "secure")] + async fn selector(&self, sigint: &mut Signal, location_string: &str) -> bool{ + select! { + _ = sigint.recv() => { + println!("Shutting down..."); + return false; + } + Ok((socket, _)) = self.address.accept() => { + let mountpoints = self.mountpoints.clone().unwrap(); + let Ok(socket) = self.tls_acceptor.accept(socket).await else { + eprintln!("\x1b[31mClient used http, not https\x1b[0m"); + return true; + }; + tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); + } + Ok((socket, _)) = self.to_secure_redirect.accept() => { + let redirect_response = Response { + headers: vec![location_string.to_string()], + cookies: None, + status: Some(Status::MovedPermanently), + body: Box::new(""), + }; + tokio::spawn(async move { let _ = redirect_response.write(socket, None).await; }); + } + } + true + } + #[cfg(not(feature = "secure"))] + async fn selector(&self, sigint: &mut Signal, _location_string: &str) -> bool { + select! { + _ = sigint.recv() => { + println!("Shutting down..."); + return false; + } + Ok((socket, _)) = self.address.accept() => { + let mountpoints = self.mountpoints.clone().unwrap(); + tokio::spawn(async move { handle_connection(socket, mountpoints).await; }); + } + } + true + } + } + /// # Creates a Webserver Config which can be launched with the launch function /// Takes the IP and Port as an argument /// @@ -139,36 +175,60 @@ impl<'a> Config { /// # Panics /// Panics if the IP is not bindable, or other forms of system errors or it's not a valid /// IP-Address -pub async fn build(ip_http: &str, ip_secure: &str) -> Config { - let Ok(listener_secure) = TcpListener::bind(ip_secure).await else { +#[cfg(feature = "secure")] +pub async fn build(ip: &str, ip_http: &str) -> Config { + let Ok(listener) = TcpListener::bind(ip).await else { panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); }; - let ip = ip_secure.splitn(2, ':').collect::<Vec<&str>>(); - if ip.len() != 2 { + let ip_vec = ip.splitn(2, ':').collect::<Vec<&str>>(); + if ip_vec.len() != 2 { panic!("Invalid IP Address"); } - let Ok(listener_http) = TcpListener::bind(ip_http).await else { panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); }; let identity = Identity::from_pkcs12(include_bytes!("certificates/identity.pfx"), "1234").unwrap(); - let port = ip[1]; - let ip = ip[0]; + let port = ip_vec[1]; + let ip = ip_vec[0]; let workers = available_parallelism().unwrap().get(); println!( - "\x1b[34m⚙ Configuration\x1b[0m +"\x1b[34m⚙ Configuration\x1b[0m >> \x1b[34mIp\x1b[0m: {ip} >> \x1b[34mPort\x1b[0m: {port} >> \x1b[34mWorkers\x1b[0m: {workers} \x1b[32m Security\x1b[0m - >> \x1b[32mHttp to Https Redirect: {ip_http} -> {ip_secure}\x1b[0m + >> \x1b[32mHttp to Https Redirect: http://{ip_http} -> https://{ip}:{port}\x1b[0m \x1b[35m🛪 Mountpoints\x1b[0m" ); Config { mountpoints: None, - address: listener_secure, + address: listener, to_secure_redirect: listener_http, tls_acceptor: native_tls::TlsAcceptor::builder(identity).build().unwrap().into() } } +#[cfg(not(feature = "secure"))] +pub async fn build(ip: &str) -> Config { + let Ok(listener) = TcpListener::bind(ip).await else { + panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); + }; + let ip_vec = ip.splitn(2, ':').collect::<Vec<&str>>(); + if ip_vec.len() != 2 { + panic!("Invalid IP Address"); + } + let port = ip_vec[1]; + let ip = ip_vec[0]; + let workers = available_parallelism().unwrap().get(); + println!( +"\x1b[34m⚙ Configuration\x1b[0m + >> \x1b[34mIp\x1b[0m: {ip} + >> \x1b[34mPort\x1b[0m: {port} + >> \x1b[34mWorkers\x1b[0m: {workers} +\x1b[35m🛪 Mountpoints\x1b[0m" + ); + Config { + mountpoints: None, + address: listener, + } +} diff --git a/examples/1.rs b/examples/1.rs new file mode 100644 index 0000000000000000000000000000000000000000..8d6483429f19c02d5d2559d6d4821bd1f30cd112 --- /dev/null +++ b/examples/1.rs @@ -0,0 +1,113 @@ + +use std::{collections::HashMap, path::PathBuf}; + +use http::handling::{ + file_handlers::NamedFile, + methods::Method, + request::{Request, ParseFormError}, + response::{Outcome, Response, Status}, + routes::{Data, Route}, +}; + +fn hashmap_to_string(map: &HashMap<&str, Result<&str, ParseFormError>>) -> String { + let mut result = String::new(); + for (key, value) in map { + result.push_str(key); + result.push('='); + result.push_str(value.as_ref().unwrap()); + result.push(';'); + } + result.pop(); // Remove the trailing semicolon if desired + result +} + +fn handle_static_hi(request: Request<>, data: Data) -> Outcome<Response, Status, Data> { + let keys = if let Ok(keys) = request.get_get_form_keys(&["asdf", "jkl"]) { + keys + } else { + return Outcome::Forward(data); + }; + let response = hashmap_to_string(&keys); + Outcome::Success(Response { + headers: vec![], + cookies: None, + status: Some(Status::Ok), + body: Box::new(response), + }) + // Outcome::Forward(data) +} + +fn handler(request: Request<>, _data: Data) -> Outcome<Response, Status, Data> { + let response = fileserver(request.uri.raw_string().unwrap().strip_prefix("static/").unwrap()); + let response = match response { + Ok(dat) => Response { + headers: vec![], + cookies: None, + status: Some(Status::Ok), + body: Box::new(dat), + }, + Err(_) => return Outcome::Failure(Status::NotFound), + }; + Outcome::Success(response) +} + +fn fileserver(path: &str) -> Result<NamedFile, Status> { + NamedFile::open(PathBuf::from("static/".to_string() + path)) +} + +fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Data> { + if data.is_empty() { + return Outcome::Forward(data); + } + let dat = if let Ok(val) = request.get_post_data(&["message"], &data) { + post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string()) + } else { + return Outcome::Failure(Status::BadRequest); + }; + Outcome::Success(Response { + headers: vec![], + cookies: None, + status: Some(Status::Ok), + body: Box::new(dat), + }) +} + +fn post_hi(msg: String) -> String { + msg +} + +#[tokio::main] +async fn main() { + let fileserver = Route { + format: None, + handler, + name: Some("file_server"), + uri: "static/<path..>", + method: Method::Get, + rank: 1, + }; + + let post_test = Route { + format: None, + handler: post_hi_handler, + name: Some("post_test"), + uri: "post", + method: Method::Post, + rank: 0, + }; + + let static_hi = Route { + format: None, + handler: handle_static_hi, + name: Some("Handle_Static_hi"), + uri: "static/hi", + method: Method::Get, + rank: 0, + }; + http::build("127.0.0.1:8080", "127.0.0.1:8443") + .await + .mount("/", vec![fileserver, post_test, static_hi]) + .mount("/post/", vec![post_test]) + .launch() + .await; +} diff --git a/site/Cargo.toml b/site/Cargo.toml index 55ff9b16a10f1446581c639bb4ec189ef3341af9..1d8b4005c671ad045ca83a15315ba430f0a5f99f 100644 --- a/site/Cargo.toml +++ b/site/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] -http = { path = "../core/http" } +http = { path = "../core/http", features = ["secure"]} tokio = { version = "1.28.2", features = ["full"] } diff --git a/site/src/main.rs b/site/src/main.rs index ffa61b16091b67e3ad0d80b16bd99be5169bf42e..c4b515a2aa4d3fcb2d347f6a247b6d15f55f4e4e 100644 --- a/site/src/main.rs +++ b/site/src/main.rs @@ -1,113 +1,28 @@ -use std::{collections::HashMap, path::PathBuf}; +use http::handling::{methods::Method, routes::{Route, Data}, request::Request, response::{Response, Outcome, Status}, file_handlers::NamedFile}; -use http::handling::{ - file_handlers::NamedFile, - methods::Method, - request::{Request, ParseFormError}, - response::{Outcome, Response, Status}, - routes::{Data, Route}, -}; - -fn hashmap_to_string(map: &HashMap<&str, Result<&str, ParseFormError>>) -> String { - let mut result = String::new(); - for (key, value) in map { - result.push_str(key); - result.push('='); - result.push_str(value.as_ref().unwrap()); - result.push(';'); - } - result.pop(); // Remove the trailing semicolon if desired - result -} - -fn handle_static_hi(request: Request<>, data: Data) -> Outcome<Response, Status, Data> { - let keys = if let Ok(keys) = request.get_get_form_keys(&["asdf", "jkl"]) { - keys - } else { - return Outcome::Forward(data); - }; - let response = hashmap_to_string(&keys); - Outcome::Success(Response { - headers: vec![], - cookies: None, - status: Some(Status::Ok), - body: Box::new(response), - }) - // Outcome::Forward(data) -} - -fn handler(request: Request<>, _data: Data) -> Outcome<Response, Status, Data> { - let response = fileserver(request.uri.raw_string().unwrap().strip_prefix("static/").unwrap()); - let response = match response { - Ok(dat) => Response { - headers: vec![], - cookies: None, - status: Some(Status::Ok), - body: Box::new(dat), - }, - Err(_) => return Outcome::Failure(Status::NotFound), - }; - Outcome::Success(response) -} - -fn fileserver(path: &str) -> Result<NamedFile, Status> { - NamedFile::open(PathBuf::from("static/".to_string() + path)) -} - -fn post_hi_handler(request: Request, data: Data) -> Outcome<Response, Status, Data> { - if data.is_empty() { - return Outcome::Forward(data); - } - let dat = if let Ok(val) = request.get_post_data(&["message"], &data) { - post_hi(String::from_utf8_lossy(val.get("message").unwrap().as_ref().unwrap()).to_string()) - } else { - return Outcome::Failure(Status::BadRequest); - }; - Outcome::Success(Response { - headers: vec![], - cookies: None, - status: Some(Status::Ok), - body: Box::new(dat), - }) +fn index_handler(_request: Request, _data: Data) -> Outcome<Response, Status, Data> { + Outcome::Success(Response { headers: vec![], cookies: None, status: None, body: Box::new(index()) }) } -fn post_hi(msg: String) -> String { - msg +fn index() -> NamedFile { + NamedFile::open("templates/index.html".into()).unwrap() } #[tokio::main] async fn main() { - let fileserver = Route { - format: None, - handler, - name: Some("file_server"), - uri: "static/<path..>", - method: Method::Get, - rank: 1, - }; - - let post_test = Route { - format: None, - handler: post_hi_handler, - name: Some("post_test"), - uri: "post", - method: Method::Post, - rank: 0, - }; - - let static_hi = Route { + let index_route = Route { format: None, - handler: handle_static_hi, - name: Some("Handle_Static_hi"), - uri: "static/hi", + handler: index_handler, + name: Some("Index"), + uri: "", method: Method::Get, rank: 0, }; - http::build("127.0.0.1:8080", "127.0.0.1:8443") + // http::build("127.0.0.1:8000") + http::build("127.0.0.1:8443", "127.0.0.1:8080") .await - .mount("/", vec![fileserver, post_test, static_hi]) - .mount("/post/", vec![post_test]) + .mount("/", vec![index_route]) .launch() .await; } diff --git a/site/static/hello.css b/site/static/hello.css deleted file mode 100644 index aba0df0603e473b6811c64f41e53ebd3559100d9..0000000000000000000000000000000000000000 --- a/site/static/hello.css +++ /dev/null @@ -1,4 +0,0 @@ -body { - background-color: #212121; - color: #ffffff; -} diff --git a/site/static/hello.html b/site/static/hello.html index ff042af8d7af0972f5997ef4813dafbe995dc9b2..bf12019a992d72484315f1250c4488fba1638b0d 100644 --- a/site/static/hello.html +++ b/site/static/hello.html @@ -1,9 +1,9 @@ -<!DOCTYPE html> +<!doctype html> <html> <head> <meta charset="UTF-8" /> <title>Hello</title> - <link rel="stylesheet" href="/static/hello.css" /> + <link rel="stylesheet" href="/static/style.css" /> </head> <body> <h1>Managed</h1> diff --git a/site/static/style.css b/site/static/style.css new file mode 100644 index 0000000000000000000000000000000000000000..27589ec6d20134a278d02f57fe10c5f40ea056e5 --- /dev/null +++ b/site/static/style.css @@ -0,0 +1,16 @@ +body { + background-color: #212121; + color: #ffffff; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + Oxygen, + Ubuntu, + Cantarell, + "Open Sans", + "Helvetica Neue", + sans-serif; +} diff --git a/site/templates/index.html b/site/templates/index.html new file mode 100644 index 0000000000000000000000000000000000000000..8645f34c319e16a7249b334f8fa955a3fdd6ed01 --- /dev/null +++ b/site/templates/index.html @@ -0,0 +1,11 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <title>LMS - The Library Management System</title> + <link rel="stylesheet" href="/static/style.css" /> + </head> + <body> + <h1>Hello and Welcome</h1> + </body> +</html>