Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • codecraft/webserver
  • sorcaMriete/webserver
  • opnonmicgo/webserver
  • loposuezo/webserver
  • diufeYmike/webserver
  • contbuspecmi/webserver
  • mogamuboun/webserver
  • glabalwelre/webserver
8 results
Show changes
Showing
with 7410 additions and 11 deletions
use std::error::Error;
use crate::utils::url_utils::UriError;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum SameSiteError {
NotAValidVariant,
}
#[derive(Debug)]
pub struct ParseSameSiteError {
pub inner: SameSiteError,
}
impl Error for ParseSameSiteError {}
impl std::fmt::Display for ParseSameSiteError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl Error for ParseCookieError {}
#[derive(Debug)]
pub struct ParseCookieError {
pub inner: CookieError,
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
pub enum CookieError {
MissingEqual,
InvalidAttribute,
InvalidMaxAge,
NoSetCookieHeader,
InvilidSameSite(SameSiteError),
PathError(UriError),
}
impl std::fmt::Display for ParseCookieError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ParseCookieError {{ error: {:?} }}", self.inner)
}
}
mod cookie;
mod cookie_builder;
mod error_types;
pub use cookie::Cookie;
pub use cookie::SameSite;
pub use cookie_builder::CookieBuilder;
pub use error_types::CookieError;
pub use error_types::ParseCookieError;
pub use error_types::ParseSameSiteError;
pub use error_types::SameSiteError;
use super::{Cookie, ResponseBody, Status};
type HeaderMap = Vec<String>;
#[derive(Debug)]
/// Enum for the result of a Handling Function, where...
///
/// [Outcome::Success] represents that the route
/// was successful and the Answer is contained in \[S\].
/// [Outcome::Failure] represents that it was unsuccessful and nobody else is going to be
/// successful. \[E\] represnts the Error Code.
/// [Outcome::Forward] represents that some requirements weren't met for a route to be working with
/// the request so the next one that matches should cover that \[F\] represents the maybe processed
/// data of the request.
///
/// # Example
/// ```
/// use http::handling::{response::{Outcome, Response, Status}, routes::Data, request::Request};
/// fn handler(request: Request, _data: Data) -> Outcome<Response, Status, Data> {
/// todo!()
/// }
/// ```
pub enum Outcome<S, E, F> {
Success(S),
Failure(E),
Forward(F),
}
/// Response is a wrapper for http responses.
#[derive(Debug)]
pub struct Response {
/// the [`Vec<String>`] of headers unrelated to `Content-Type` and `Content-Length`
pub headers: HeaderMap,
/// Optional Cookie in the response
pub cookies: Option<Cookie>,
/// Status code of the response
pub status: Option<Status>,
/// Response body and `Content-Type` and `Content-Length` headers.
pub body: Box<dyn ResponseBody>,
}
mod build_and_write;
mod cookie_management;
mod datatypes;
mod status;
mod traits;
pub use cookie_management::Cookie;
pub use cookie_management::CookieBuilder;
pub use cookie_management::SameSite;
pub use datatypes::Outcome;
pub use datatypes::Response;
pub use status::Status;
pub use traits::ResponseBody;
use std::fmt::Display;
#[derive(Debug)]
/// Enum With every http status for complete documentation [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
pub enum Status {
Continue,
SwitchingProtocols,
/// WebDAV
Processing,
/// Experimental
EarlyHints,
Ok,
Created,
Accepted,
NonAuthorativeIfnormation,
NoContent,
ResetContent,
PartialContent,
/// WebDAV
MultiStatus,
/// WebDAV
AlreadyReported,
HttpDataEncodingImUsed,
MultipleChoices,
MovedPermanently,
Found,
SeeOther,
NotModfiied,
/// Deprecated
UseProxy,
/// Deprecated
Unused,
TemporaryRedirect,
PermanentRedirect,
BadRequest,
Unauthorized,
/// Experimental
PaymentRequired,
Forbidden,
NotFound,
MethodNotAllowed,
NotAcceptable,
ProxyAuthenticationRequired,
RequestTimeout,
Conflict,
Gone,
LengthRequired,
PreconditionFailed,
PayloadTooLarge,
UriTooLong,
UnsupportedMediaType,
RangeNotSatisfiable,
ExpectationFailed,
ImATeapot,
MisdirectedRequest,
/// WebDAV
UnprocessableContent,
/// WebDAV
Locked,
/// WebDAV
FailedDependency,
/// Experimental
TooEarly,
UpgradeRequred,
PreconditionRequired,
TooManyRequests,
RequestHeaderFieldsTooLarge,
UnavailableForLegalReasons,
InternalServerError,
NotImplemented,
BadGetaway,
ServiceUnavailable,
GetawayTimeout,
HttpVersionNotSupported,
VariantAlsoNegotiates,
/// WebDAV
InsufficientStorage,
/// WebDAV
LoopDetected,
NotExtended,
NetworkAuthenticationRequired,
}
impl Display for Status {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Status::Continue => write!(f, "100 Continue"),
Status::SwitchingProtocols => write!(f, "101 Switching Protocols"),
Status::Processing => write!(f, "102 Processing"),
Status::EarlyHints => write!(f, "103 Early Hints"),
Status::Ok => write!(f, "200 OK"),
Status::Created => write!(f, "201 Created"),
Status::Accepted => write!(f, "202 Accepted"),
Status::NonAuthorativeIfnormation => write!(f, "203 Non-Authorative Information"),
Status::NoContent => write!(f, "204 No Content"),
Status::ResetContent => write!(f, "205 Reset Content"),
Status::PartialContent => write!(f, "206 Partial Content"),
Status::MultiStatus => write!(f, "207 Mutli-Status"),
Status::AlreadyReported => write!(f, "208 Already Reported"),
Status::HttpDataEncodingImUsed => write!(f, "226 IM Used"),
Status::MultipleChoices => write!(f, "300 Multiple Choices"),
Status::MovedPermanently => write!(f, "301 Moved Permanently"),
Status::Found => write!(f, "302 Found"),
Status::SeeOther => write!(f, "303 See Other"),
Status::NotModfiied => write!(f, "304 Not Modified"),
Status::TemporaryRedirect => write!(f, "307 Temporary Redirect"),
Status::PermanentRedirect => write!(f, "308 Permanent Redirect"),
Status::UseProxy => write!(f, "305 Use Proxy"),
Status::Unused => write!(f, "306 unused"),
Status::BadRequest => write!(f, "400 Bad Request"),
Status::Unauthorized => write!(f, "401 Unauthorized"),
Status::PaymentRequired => write!(f, "402 Payment Required"),
Status::Forbidden => write!(f, "403 Forbidden"),
Status::NotFound => write!(f, "404 Not Found"),
Status::MethodNotAllowed => write!(f, "405 Method Not Allowed"),
Status::NotAcceptable => write!(f, "406 Not Acceptable"),
Status::ProxyAuthenticationRequired => {
write!(f, "407 Proxy Athentication Required")
}
Status::RequestTimeout => write!(f, "408 Request Timout"),
Status::Conflict => write!(f, "409 Conflict"),
Status::Gone => write!(f, "410 Gone"),
Status::LengthRequired => write!(f, "411 Length Required"),
Status::PreconditionFailed => write!(f, "412 Precondition Failed"),
Status::PayloadTooLarge => write!(f, "413 Payload Too Large"),
Status::UriTooLong => write!(f, "414 URI Too Long"),
Status::UnsupportedMediaType => write!(f, "415 Unsupported Media Type"),
Status::RangeNotSatisfiable => write!(f, "416 Range Not Satisfiable"),
Status::ExpectationFailed => write!(f, "417 Expectation Failed"),
Status::ImATeapot => write!(f, "418 I'm a Teapot"),
Status::MisdirectedRequest => write!(f, "421 Misdirected Request"),
Status::UnprocessableContent => write!(f, "422 Unprocessable Content"),
Status::Locked => write!(f, "423 Locked"),
Status::FailedDependency => write!(f, "424 Failed Dependency"),
Status::TooEarly => write!(f, "425 Too Early"),
Status::UpgradeRequred => write!(f, "426 Upgrade Required"),
Status::PreconditionRequired => write!(f, "428 Precondition Required"),
Status::TooManyRequests => write!(f, "429 Too Many Requests"),
Status::RequestHeaderFieldsTooLarge => {
write!(f, "431 Request Header Fields Too Large")
}
Status::UnavailableForLegalReasons => {
write!(f, "451 Unavailable For Legal Reasons")
}
Status::InternalServerError => write!(f, "500 Internal Server Error"),
Status::NotImplemented => write!(f, "501 Not Implmenented"),
Status::BadGetaway => write!(f, "502 Bad Getaway"),
Status::ServiceUnavailable => write!(f, "503 Service Unavailable"),
Status::GetawayTimeout => write!(f, "504 Getaway Timeout"),
Status::HttpVersionNotSupported => write!(f, "505 HTTP Version Not Supported"),
Status::VariantAlsoNegotiates => write!(f, "506 Variant Also Negotiates"),
Status::InsufficientStorage => write!(f, "507 Insufficient Storage"),
Status::LoopDetected => write!(f, "508 Loop Detected"),
Status::NotExtended => write!(f, "510 Not Extendend"),
Status::NetworkAuthenticationRequired => {
write!(f, "511 Network Authentication Required")
}
}
}
}
use std::fmt::Debug;
use crate::{handling::routes::Body, utils::mime::Mime};
/// Trait for using datatypes as response bodies
pub trait ResponseBody: Send + Debug {
/// Get a cloned version of the data as a [`Vec<u8>`]
/// # Ecamples
/// ```
/// use http::handling::response::ResponseBody;
/// let data = "DATA";
/// assert_eq!(b"DATA".to_vec(), data.get_data());
/// ```
fn get_data(&self) -> Vec<u8>;
/// get the miem type of the data as a [Mime]
/// # Examples
/// ```
/// use http::handling::response::ResponseBody;
/// use http::utils::mime::Mime;
/// let data = "DATA";
/// assert_eq!(Mime::TextPlain, data.get_mime());
/// ```
fn get_mime(&self) -> Mime;
/// get the length in bytes of the data as a [usize]
/// # Examples
/// ```
/// use http::handling::response::ResponseBody;
/// let data = "DATA";
/// assert_eq!(4, data.get_len());
/// ```
fn get_len(&self) -> usize;
}
impl ResponseBody for Body {
fn get_data(&self) -> Vec<u8> {
self.body()
}
fn get_mime(&self) -> Mime {
self.mime_type()
}
fn get_len(&self) -> usize {
self.get_data().len()
}
}
impl ResponseBody for &str {
fn get_data(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
fn get_mime(&self) -> Mime {
Mime::TextPlain
}
fn get_len(&self) -> usize {
self.len()
}
}
impl ResponseBody for String {
fn get_data(&self) -> Vec<u8> {
self.as_bytes().to_vec()
}
fn get_mime(&self) -> Mime {
Mime::TextPlain
}
fn get_len(&self) -> usize {
self.len()
}
}
use crate::{
handling::{
methods::Method,
request::{MediaType, Request},
response::{Outcome, Response, Status},
},
utils::mime::Mime,
};
/// A RouteBuilder struct
pub struct RoutBuilder {
/// An optional name of the route
name: Option<&'static str>,
/// The [Method] via which the route is accesable
method: Method,
/// The path of the route, allows special cases:
/// # Examples
/// ```
/// "/home"; // Only /home
/// "/<home>/something";
/// // Variable content the users provides this acts for /<anything>/something
/// "/<home..>";
/// // All Information after this sequence is irrelvent
/// // Matches: /a, /a/b/c ...
/// ```
path: &'static str,
/// The Handler function for this route, which gets called when the request need the route.
/// Inputs to the function are an [Request] and the [Data] which represents the body of the
/// [Request]. The Outcome is expected to be an [Outcome], which is a [Response], A [Status] if
/// something went wrong and a [Status] page is need or a [Outcome::Forward] of the requests
/// [Data] for the next [Route] to take care of.
handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
/// The Specific answer format of the [Route] as a [MediaType]. Optional
format: Option<MediaType>,
/// The Optional Rank of the Route, dependent on its specificness. so the rank of a uri `"/home"` would be
/// ranked high, whereas a uri of `"/<anything..>"` would be ranked the lowest
/// If not given generated based on parematers.
rank: Option<isize>,
}
/// A struct to define Routes on the Server
#[derive(Clone, Copy)]
pub struct Route<'a> {
/// An optional name of the route
pub name: Option<&'static str>,
/// The [Method] via which the route is accesable
pub method: Method,
/// The Uri of the route, allows special cases:
/// # Examples
/// ```
/// "/home"; // Only /home
/// "/<home>/something";
/// // Variable content the users provides this acts for /<anything>/something
/// "/<home..>";
/// // All Information after this sequence is irrelvent
/// // Matches: /a, /a/b/c ...
/// ```
pub uri: Uri<'a>,
/// The Handler function for this route, which gets called when the request need the route.
/// Inputs to the function are an [Request] and the [Data] which represents the body of the
/// [Request]. The Outcome is expected to be an [Outcome], which is a [Response], A [Status] if
/// something went wrong and a [Status] page is need or a [Outcome::Forward] of the requests
/// [Data] for the next [Route] to take care of.
pub handler: fn(Request, Data) -> Outcome<Response, Status, Data>,
/// The Rank of the Route, dependent on its specificness. so the rank of a uri `"/home"` would be
/// ranked high, whereas a uri of `"/<anything..>"` would be ranked the lowest
pub rank: isize,
/// The Specific answer format of the [Route] as a [MediaType]. Optional
pub format: Option<MediaType>,
}
impl Route<'_> {
/// generates a Route from a Routebuilder
//TODO: ranking
pub fn from(routeinfo: RoutBuilder) -> Self {
let rank = routeinfo.rank.unwrap_or(0);
Route {
name: routeinfo.name,
method: routeinfo.method,
uri: routeinfo.path,
handler: routeinfo.handler,
rank,
format: routeinfo.format,
}
}
/// Matches a [Request] Uri with a [Route] Uri. Respecting special cases like `?` and `<a..>`
pub fn compare_uri(&self, uri: Uri) -> bool {
let mut iter_comp_str = uri.split('/');
for true_str in self.uri.split('/') {
let comp_str = if let Some(str) = iter_comp_str.next() {
str
} else {
return false;
};
if (true_str.starts_with('<') && true_str.ends_with("..>"))
|| (comp_str.starts_with(true_str) && comp_str.contains('?'))
{
return true;
}
if true_str.starts_with('<') && true_str.ends_with('>') {
continue;
}
if true_str != comp_str {
return false;
}
}
true
}
}
/// Alias for using a &'a str for Uri
pub type Uri<'a> = &'a str;
#[derive(Debug, Clone)]
/// A basic Body type for respones
pub struct Body {
/// The Response body
body: Vec<u8>,
/// The Mime Type
mime_type: Mime,
}
impl Body {
/// New body of a Response
pub fn new(body: Vec<u8>, mime_type: Mime) -> Self {
Self { body, mime_type }
}
/// Sets the `mime_type` of the Body
pub fn set_mime_type(&mut self, mime_type: Mime) {
self.mime_type = mime_type;
}
/// Reassigns the body
pub fn set_body(&mut self, body: Vec<u8>) {
self.body = body;
}
/// mime_type of the body
pub fn mime_type(&self) -> Mime {
self.mime_type
}
/// cloned body as [`Vec<u8>`]
pub fn body(&self) -> Vec<u8> {
self.body.clone()
}
}
#[derive(Debug, Clone)]
/// Data of the Body of a [Request]
pub struct Data {
/// The Data
pub buffer: Vec<u8>,
/// For Split Data if it is complete
pub is_complete: bool,
}
impl Data {
/// Checks if the buffer.oen() is -0
pub fn is_empty(&self) -> bool {
self.buffer.len() == 0
}
}
pub fn add(left: usize, right: usize) -> usize {
left + right
}
pub mod handlers;
pub mod handling;
mod setup;
pub mod utils;
#[cfg(test)]
mod tests {
use super::*;
mod tests {}
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
pub use setup::build;
use std::thread::available_parallelism;
use tokio::{
net::TcpListener,
select,
signal::unix::{signal, SignalKind, Signal},
};
#[cfg(feature = "secure")]
use tokio_native_tls::{native_tls::{Identity, self}, TlsAcceptor};
use crate::{
handlers::handler::handle_connection,
handling::routes::{Route, Uri},
};
#[cfg(feature = "secure")]
use crate::handling::response::{Response, Status};
#[derive(Clone)]
/// Represnts a [MountPoint] that can be mounted in the config
pub struct MountPoint<'a> {
/// The prefix of the [MountPoint]
pub mountpoint: Uri<'a>,
/// All Routes mounted on the [MountPoint]. The Routes are all prefixed by the mountpoints
/// mountpoint
pub routes: Vec<Route<'a>>,
}
/// A server configuration that is run
pub struct Config {
/// Contains an Optional [`Vec<MountPoint>`]. which contains all [MountPoint]s on the Server.
/// Which contain all the [Route]s
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,
}
impl<'a> Config {
/// Utility that checks if the given mointpoint is already taken. takes in the uri of the to be
/// mounted mountpoint
fn check_mountpoint_taken(&self, to_insert: Uri) -> bool {
if let Some(to_check) = &self.mountpoints {
for i in to_check.iter() {
if i.mountpoint == to_insert {
return true; // Found a duplicate &str
}
}
};
false
}
/// mounts a [MountPoint] on the [Config] takes in a blank [MountPoint] and the [Route]s to be
/// mounted, mounts them and inserts the new [MountPoint]. Also sorts by rank.
pub fn mount(mut self, mountpoint: Uri<'static>, mut routes: Vec<Route<'static>>) -> Self {
if self.check_mountpoint_taken(mountpoint) {
eprintln!("\x1b[31mTrying to reassign a mountpoint, mountpoint `{mountpoint}` already taken.\x1b[0m");
return self;
}
routes.sort_by(|a, b| a.rank.cmp(&b.rank));
let mut mount_message = format!(" >> \x1b[35m{}\x1b[0m\n", mountpoint);
for (index, route) in routes.iter().enumerate() {
let indent_sign = match index {
i if i == routes.len() - 1 => "└─",
_ => "├─",
};
mount_message += &format!(
" \x1b[35m{indent_sign}\x1b[0m \x1b[36m(\x1b[0m{}\x1b[36m)\x1b[0m \x1b[32m{}\x1b[0m \x1b[34;4m{}\x1b[24m{}\x1b[0m\n",
route.name.unwrap_or(""),
route.method,
mountpoint,
route.uri
)
}
println!("{mount_message}");
let mut temp_mountpoints = None;
std::mem::swap(&mut self.mountpoints, &mut temp_mountpoints);
if let Some(mut mountpoints) = temp_mountpoints {
mountpoints.push(MountPoint { mountpoint, routes });
self.mountpoints = Some(mountpoints);
} else {
self.mountpoints = Some(vec![MountPoint { mountpoint, routes }]);
}
self
}
/// # Launches/Starts the webserver
/// Launches a Webserver Configuration
///
/// Is Async
///
/// Is Blocking -> Can be interrupted with ^C
///
/// # Panics
/// 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) {
{
#[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 {
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
///
/// Prints out the configuration test
///
/// Is async
///
/// # Example
/// ```
/// async fn example() {
/// let _ = http::build("127.0.0.1:8080", "127.0.0.1:8443");
/// }
/// ```
/// # Panics
/// Panics if the IP is not bindable, or other forms of system errors or it's not a valid
/// IP-Address
#[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_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_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[32m Security\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,
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,
}
}
This diff is collapsed.
This diff is collapsed.
mod map;
mod mime_enum;
pub use map::MIME_MAP;
pub use mime_enum::Mime;
pub use mime_enum::ParseMimeError;
pub mod mime;
pub mod url_utils;
pub mod urlencoded;
use std::error;
use crate::utils::urlencoded::UrlEncodeData;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct Uri {
pub(super) parts: Vec<UrlEncodeData>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RawUri {
pub(super) raw_string: String,
pub(super) infinte_end: bool,
pub(super) parts: Vec<RawUriElement>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum RawUriElement {
Variable,
Name(UrlEncodeData),
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum UriError {
InvalidUriEncoding,
}
#[derive(Debug)]
pub struct ParseUriError {
pub error: UriError,
}
impl std::fmt::Display for ParseUriError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl error::Error for ParseUriError {}
mod datatypes;
mod uri;
pub use datatypes::ParseUriError;
pub use datatypes::RawUri;
pub use datatypes::RawUriElement;
pub use datatypes::Uri;
pub use datatypes::UriError;
use std::str::FromStr;
use crate::utils::{url_utils::datatypes::RawUriElement, urlencoded::UrlEncodeData};
use super::datatypes::{ParseUriError, RawUri, Uri, UriError};
impl Uri {
pub fn new(parts: Vec<&str>) -> Self {
Self {
parts: parts.into_iter().map(UrlEncodeData::from_raw).collect(),
}
}
}
impl RawUri {
pub fn new(parts: Vec<&str>) -> Self {
let mut result = Self {
infinte_end: false,
parts: Vec::with_capacity(parts.len()),
raw_string: "/".to_owned() + &parts.join("/"),
};
for part in parts {
if part.starts_with('<') && part.ends_with("..>") {
result.infinte_end = true;
break;
}
if part.starts_with('<') && part.ends_with('>') {
result.parts.push(RawUriElement::Variable);
continue;
}
result
.parts
.push(RawUriElement::Name(UrlEncodeData::from_raw(part)))
}
result
}
pub fn compare_uri(self, uri: Uri) -> bool {
let mut iter_comp = uri.parts.iter();
let mut counter = 0;
for element in self.parts.iter() {
counter += 1;
let Some(compare_element) = iter_comp.next() else {
return false;
};
if *element == RawUriElement::Variable {
continue;
}
if let RawUriElement::Name(name) = element {
if name.encoded() != compare_element.encoded() {
return false;
}
} else {
return false;
}
}
if counter > self.parts.len() && !self.infinte_end {
return false;
}
true
}
}
impl std::fmt::Display for RawUri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.raw_string)
}
}
impl std::fmt::Display for Uri {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let url = self
.parts
.iter()
.map(|part| part.encoded())
.collect::<Vec<_>>();
write!(f, "/{}", url.join("/"))
}
}
impl FromStr for Uri {
type Err = ParseUriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let split = s.split('/');
let mut result = Vec::new();
for sub in split {
if sub.is_empty() {
continue;
}
result.push(if let Ok(coded) = UrlEncodeData::from_encoded(sub) {
coded
} else {
UrlEncodeData::from_raw(sub)
});
}
Ok(Self { parts: result })
}
}
impl FromStr for RawUri {
type Err = UriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts = s.split('/').collect::<Vec<&str>>();
let mut result = Self {
infinte_end: false,
parts: Vec::new(),
raw_string: parts.join("/"),
};
for part in parts {
if part.is_empty() {
continue;
}
if part.starts_with('<') && part.ends_with("..>") {
result.infinte_end = true;
break;
}
if part.starts_with('<') && part.ends_with('>') {
result.parts.push(RawUriElement::Variable);
continue;
}
result
.parts
.push(RawUriElement::Name(UrlEncodeData::from_raw(part)))
}
Ok(result)
}
}
#[cfg(test)]
mod test {
use crate::utils::{
url_utils::{
datatypes::{RawUri, RawUriElement},
Uri,
},
urlencoded::UrlEncodeData,
};
#[test]
fn uri_to_string() {
assert_eq!("/a/%20", Uri::new(vec!["a", " "]).to_string());
}
#[test]
fn uri_from_string() {
assert_eq!(Uri::new(vec!["a", " "]), "/a/%20".parse().unwrap())
}
#[test]
fn raw_uri_from_string() {
assert_eq!(
RawUri {
raw_string: "/<name>/a/<f..>".to_owned(),
infinte_end: true,
parts: vec![
RawUriElement::Variable,
RawUriElement::Name(UrlEncodeData::from_raw("a")),
]
},
"/<name>/a/<f..>".parse().unwrap()
);
}
#[test]
fn raw_uri_to_string() {
assert_eq!(
"/<name>/a/<f..>",
RawUri {
raw_string: "/<name>/a/<f..>".to_owned(),
infinte_end: true,
parts: vec![
RawUriElement::Variable,
RawUriElement::Name(UrlEncodeData::from_raw("a")),
]
}
.to_string()
)
}
}
use crate::utils::urlencoded::endecode::EnCodable;
use super::endecode::DeCodable;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
/// A way to store UrlEncoded data
pub struct UrlEncodeData {
/// Encoded string
pub(crate) encoded: String,
/// raw data, unencoded
pub(crate) raw: Vec<u8>,
/// raw string if it exists
pub(crate) raw_string: Option<String>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum UrlEncodeError {
NonHexAfterPercent,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ParseUrlEncodeError {
pub inner: UrlEncodeError,
}
impl std::fmt::Display for ParseUrlEncodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for ParseUrlEncodeError {}
impl UrlEncodeData {
/// Generates a [UrlEncodeData] from any raw data that can be a slice of [u8]
pub fn from_raw<T: AsRef<[u8]>>(raw: T) -> Self {
Self {
raw: raw.as_ref().to_owned(),
encoded: raw.encode(),
raw_string: String::from_utf8(raw.as_ref().into()).ok(),
}
}
/// Generates a [UrlEncodeData] from a correctly encoded string
///
/// # Errors
///
/// errors if the encoded data is wrongly encoded -> %<invalid_character>
pub fn from_encoded(encoded: &str) -> super::UriParseResult<Self> {
Ok(Self {
encoded: encoded.to_owned(),
raw: encoded.decode()?,
raw_string: String::from_utf8(encoded.decode()?).ok(),
})
}
/// Gets a reference to the encoded data
pub fn encoded(&self) -> &str {
self.encoded.as_ref()
}
/// Get a reference to the raw [u8] slice
pub fn raw(&self) -> &[u8] {
self.raw.as_ref()
}
/// Gets an Optional string slice to the raw data
pub fn raw_string(&self) -> Option<&str> {
self.raw_string.as_deref()
}
}
impl std::fmt::Display for UrlEncodeData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.encoded)
}
}
use super::{datatypes::ParseUrlEncodeError, UriParseResult};
/// Base of the HexaDecimal Number system
static BASE16_HEXA_DECIMAL: u8 = 0x10;
/// Highest possible Value per digit
static BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT: u8 = 0xF;
/// Bits of a hexa decimal number
static BASE16_HEXA_DECIMAL_DIGIT_BITS: u8 = 4;
pub trait EnCodable {
/// Encodes the give data into a Percent Encoded [String]
fn encode(&self) -> String;
}
pub trait DeCodable {
/// Decodes the given data into a [`Vec<u8>`]
///
/// # Errors
/// Errors if the encoding isn't right
fn decode(&self) -> UriParseResult<Vec<u8>>;
}
impl<T: AsRef<[u8]>> EnCodable for T {
fn encode(&self) -> String {
self.as_ref().encode()
}
}
impl EnCodable for [u8] {
fn encode(self: &[u8]) -> String {
let mut result = String::with_capacity(self.len());
let result_vec = unsafe { result.as_mut_vec() };
self.iter().for_each(|byte| {
if !matches!(byte, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'-' | b'_' | b'.' | b'~')
{
result_vec.push(b'%');
result_vec.push(hex_to_digit(byte >> BASE16_HEXA_DECIMAL_DIGIT_BITS));
result_vec.push(hex_to_digit(
byte & BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT,
));
} else {
result_vec.push(*byte)
}
});
result
}
}
impl DeCodable for &str {
fn decode(&self) -> UriParseResult<Vec<u8>> {
let mut first = true;
let mut result = Vec::with_capacity(self.len());
for i in self.split('%') {
if first {
first = false;
result.extend_from_slice(i.as_bytes());
continue;
}
let Ok(char) = u8::from_str_radix(i[0..=1].as_ref(), BASE16_HEXA_DECIMAL.into()) else {
return Err(ParseUrlEncodeError { inner: super::datatypes::UrlEncodeError::NonHexAfterPercent });
};
result.push(char);
result.extend_from_slice(i[2..].as_bytes());
}
Ok(result)
}
}
/// converts a [u8] digit into the ascii code of its counterpart. The digit shouldn't be bigger
/// than [BASE16_HEXA_DECIMAL_POSSIBLE_VALUE_PER_DIGIT]
fn hex_to_digit(digit: u8) -> u8 {
match digit {
0..=9 => b'0' + digit,
10..=255 => b'A' + digit - 10,
}
}
#[cfg(test)]
mod test {
use crate::utils::urlencoded::endecode::DeCodable;
use crate::utils::urlencoded::endecode::EnCodable;
#[test]
fn urlencoded_test() {
assert_eq!(
"Darius%20is%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire",
b"Darius is the biggest genius/Genie/Human extraordin\xC3\xA4ire".encode()
);
}
#[test]
fn urldecoded_test() {
assert_eq!(
"Darius is the biggest genius/Genie/Human extraordinäire",
String::from_utf8(
"Darius%20is%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire"
.decode()
.unwrap()
)
.unwrap()
);
assert_eq!(
Err(crate::utils::urlencoded::datatypes::ParseUrlEncodeError {
inner: crate::utils::urlencoded::datatypes::UrlEncodeError::NonHexAfterPercent
}),
"Darius%2iis%20the%20biggest%20genius%2FGenie%2FHuman%20extraordin%C3%A4ire".decode()
);
assert_eq!(
"hi?asdf=sadf%%&jkl=s",
String::from_utf8("hi?asdf=sadf%25%25&jkl=s".decode().unwrap()).unwrap()
)
}
}
mod datatypes;
mod endecode;
use std::result;
type UriParseResult<T> = result::Result<T, datatypes::ParseUrlEncodeError>;
pub use datatypes::UrlEncodeData;
pub use endecode::{DeCodable, EnCodable};
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;
}