Skip to content
Snippets Groups Projects
Commit 54277faa authored by codecraft's avatar codecraft :crocodile:
Browse files

make https conditionally compiled with the secure feature

Writing some examples
Index page for lms
parent 9191168b
No related branches found
No related tags found
1 merge request!1Initial feature merge
...@@ -3,8 +3,12 @@ name = "http" ...@@ -3,8 +3,12 @@ name = "http"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[features]
secure = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
tokio = { version = "1.28.2", features = ["full"] } tokio = { version = "1.28.2", features = ["full"] }
phf = { version = "0.11", features = ["macros"] } phf = { version = "0.11", features = ["macros"] }
......
use std::{io, path::PathBuf}; use std::{io, path::PathBuf};
use tokio::{ use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead};
io::{AsyncBufReadExt, AsyncReadExt, BufReader},
net::TcpStream,
};
use tokio_native_tls::{TlsStream, TlsAcceptor};
use crate::{handling::{ use crate::{handling::{
file_handlers::NamedFile, file_handlers::NamedFile,
...@@ -29,11 +25,7 @@ static MAX_HTTP_MESSAGE_SIZE: u16 = 4196; ...@@ -29,11 +25,7 @@ static MAX_HTTP_MESSAGE_SIZE: u16 = 4196;
/// ///
/// # Panics /// # Panics
/// No Panics /// No Panics
pub async fn handle_connection(stream: TcpStream, mountpoints: Vec<MountPoint<'_>>, acceptor: TlsAcceptor) { pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint<'_>>) {
let Ok(mut stream) = acceptor.accept(stream).await else {
eprintln!("\x1b[31mClient used http, not https\x1b[0m");
return;
};
let mut buf_reader = BufReader::new(&mut stream); let mut buf_reader = BufReader::new(&mut stream);
let mut http_request: Vec<String> = Vec::with_capacity(10); let mut http_request: Vec<String> = Vec::with_capacity(10);
loop { loop {
...@@ -197,7 +189,7 @@ fn failure_handler(status: Status) -> Response { ...@@ -197,7 +189,7 @@ fn failure_handler(status: Status) -> Response {
} }
/// Handler for len_not_defined errors. writes back directly /// 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(); let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
Response { Response {
cookies: None, cookies: None,
...@@ -215,14 +207,3 @@ fn error_occured_when_writing(e: io::Error) { ...@@ -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"); 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);
// }
// }
use std::io::Result; use std::io::Result;
use tokio::{io::AsyncWriteExt, net::TcpStream}; use tokio::{io::{AsyncWriteExt, AsyncRead, AsyncWrite}, net::TcpStream};
use tokio_native_tls::TlsStream;
use crate::handling::{methods::Method, request::Request, response::Status}; use crate::handling::{methods::Method, request::Request, response::Status};
...@@ -33,7 +32,7 @@ impl Response { ...@@ -33,7 +32,7 @@ impl Response {
compiled_out compiled_out
} }
/// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response] /// 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); let resp = self.build(request);
stream.write_all(&resp).await?; stream.write_all(&resp).await?;
Ok(()) Ok(())
......
...@@ -3,8 +3,9 @@ use std::{thread::available_parallelism}; ...@@ -3,8 +3,9 @@ use std::{thread::available_parallelism};
use tokio::{ use tokio::{
net::TcpListener, net::TcpListener,
select, select,
signal::unix::{signal, SignalKind}, signal::unix::{signal, SignalKind, Signal},
}; };
use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor}; use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor};
use crate::{ use crate::{
...@@ -29,7 +30,9 @@ pub struct Config { ...@@ -29,7 +30,9 @@ pub struct Config {
mountpoints: Option<Vec<MountPoint<'static>>>, mountpoints: Option<Vec<MountPoint<'static>>>,
/// Contains a [tokio::net::TcpListener] that is bound for the server /// Contains a [tokio::net::TcpListener] that is bound for the server
address: TcpListener, address: TcpListener,
#[cfg(feature = "secure")]
to_secure_redirect: TcpListener, to_secure_redirect: TcpListener,
#[cfg(feature = "secure")]
tls_acceptor: TlsAcceptor, tls_acceptor: TlsAcceptor,
} }
...@@ -93,36 +96,69 @@ impl<'a> Config { ...@@ -93,36 +96,69 @@ impl<'a> Config {
/// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there /// Panics if there are no Mountpoints in the Confiuration to lauch, as well as when the there
/// is no interrupt signal /// is no interrupt signal
pub async fn launch(self) { pub async fn launch(self) {
println!( {
"Server launched from https://{} and http://{}", #[cfg(feature = "secure")] {
self.address.local_addr().unwrap(), self.to_secure_redirect.local_addr().unwrap() 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 mut sigint = signal(SignalKind::interrupt()).unwrap();
let location_string = format!("Location: https://{}", self.address.local_addr().unwrap()); let location_string = format!("Location: https://{}", self.address.local_addr().unwrap());
loop { loop {
select! { if !self.selector(&mut sigint, &location_string).await {
_ = sigint.recv() => { break;
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; });
}
} }
} }
} }
#[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 /// # Creates a Webserver Config which can be launched with the launch function
/// Takes the IP and Port as an argument /// Takes the IP and Port as an argument
/// ///
...@@ -139,36 +175,60 @@ impl<'a> Config { ...@@ -139,36 +175,60 @@ impl<'a> Config {
/// # Panics /// # Panics
/// Panics if the IP is not bindable, or other forms of system errors or it's not a valid /// Panics if the IP is not bindable, or other forms of system errors or it's not a valid
/// IP-Address /// IP-Address
pub async fn build(ip_http: &str, ip_secure: &str) -> Config { #[cfg(feature = "secure")]
let Ok(listener_secure) = TcpListener::bind(ip_secure).await else { 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"); panic!("\x1b[31mCould't bind Listener to address\x1b[0m");
}; };
let ip = ip_secure.splitn(2, ':').collect::<Vec<&str>>(); let ip_vec = ip.splitn(2, ':').collect::<Vec<&str>>();
if ip.len() != 2 { if ip_vec.len() != 2 {
panic!("Invalid IP Address"); panic!("Invalid IP Address");
} }
let Ok(listener_http) = TcpListener::bind(ip_http).await else { let Ok(listener_http) = TcpListener::bind(ip_http).await else {
panic!("\x1b[31mCould't bind Listener to address\x1b[0m"); panic!("\x1b[31mCould't bind Listener to address\x1b[0m");
}; };
let identity = Identity::from_pkcs12(include_bytes!("certificates/identity.pfx"), "1234").unwrap(); let identity = Identity::from_pkcs12(include_bytes!("certificates/identity.pfx"), "1234").unwrap();
let port = ip[1]; let port = ip_vec[1];
let ip = ip[0]; let ip = ip_vec[0];
let workers = available_parallelism().unwrap().get(); let workers = available_parallelism().unwrap().get();
println!( println!(
"\x1b[34m⚙ Configuration\x1b[0m "\x1b[34m⚙ Configuration\x1b[0m
>> \x1b[34mIp\x1b[0m: {ip} >> \x1b[34mIp\x1b[0m: {ip}
>> \x1b[34mPort\x1b[0m: {port} >> \x1b[34mPort\x1b[0m: {port}
>> \x1b[34mWorkers\x1b[0m: {workers} >> \x1b[34mWorkers\x1b[0m: {workers}
\x1b[32m Security\x1b[0m \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" \x1b[35m🛪 Mountpoints\x1b[0m"
); );
Config { Config {
mountpoints: None, mountpoints: None,
address: listener_secure, address: listener,
to_secure_redirect: listener_http, to_secure_redirect: listener_http,
tls_acceptor: native_tls::TlsAcceptor::builder(identity).build().unwrap().into() 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,
}
}
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;
}
...@@ -5,6 +5,7 @@ edition = "2021" ...@@ -5,6 +5,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
http = { path = "../core/http" } http = { path = "../core/http", features = ["secure"]}
tokio = { version = "1.28.2", features = ["full"] } tokio = { version = "1.28.2", features = ["full"] }
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::{ fn index_handler(_request: Request, _data: Data) -> Outcome<Response, Status, Data> {
file_handlers::NamedFile, Outcome::Success(Response { headers: vec![], cookies: None, status: None, body: Box::new(index()) })
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 { fn index() -> NamedFile {
msg NamedFile::open("templates/index.html".into()).unwrap()
} }
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let fileserver = Route { let index_route = 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, format: None,
handler: handle_static_hi, handler: index_handler,
name: Some("Handle_Static_hi"), name: Some("Index"),
uri: "static/hi", uri: "",
method: Method::Get, method: Method::Get,
rank: 0, 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 .await
.mount("/", vec![fileserver, post_test, static_hi]) .mount("/", vec![index_route])
.mount("/post/", vec![post_test])
.launch() .launch()
.await; .await;
} }
body {
background-color: #212121;
color: #ffffff;
}
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Hello</title> <title>Hello</title>
<link rel="stylesheet" href="/static/hello.css" /> <link rel="stylesheet" href="/static/style.css" />
</head> </head>
<body> <body>
<h1>Managed</h1> <h1>Managed</h1>
......
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;
}
<!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>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment