use std::{collections::HashMap, fs, future::Future, path, pin::Pin};
use actix_web::{body::BoxBody, dev::Service, http::{header::{HeaderName, HeaderValue}, StatusCode}, web::{self, resource, Payload}, HttpRequest};
use crate::{funs::{frag, root}, models::guard::start_guard_event_loop, types::{method::Method, schema::{AppRootSchema, AppSchema, FragmentRouteSchema, FragmentSchema, RewriteSchema, ServedSchema}}};
use super::{frag::Fragment, response::Response, rewrite::Rewrite, route::Route, served::Served};
pub enum ResponseTemplate {
RequestVerificationFailed(&'static str),
ResponseVerificationFailed(&'static str),
NotFound(&'static str),
InvalidRequest(&'static str),
RestrictedAccess(&'static str),
}
pub struct App<'a> {
pub root: (String, Option<Route>),
pub fragments: Vec<Box<dyn Fragment>>,
served: Vec<(Served, Box<[&'static str]>)>,
rewrites: Vec<Rewrite>,
full_global_root: &'a str,
response_templates: Vec<ResponseTemplate>,
middlewares: Vec<Box<dyn Fn(&HttpRequest) -> Pin<Box<dyn Future<Output = Result<Response, MiddlewareNext>>>>>>,
}
unsafe impl<'a> Send for App<'a> {}
unsafe impl<'a> Sync for App<'a> {}
fn match_rewrite(path: &str, rewrites: &[Rewrite]) -> Option<String> {
for rewrite in rewrites {
let from_parts = rewrite.from.split("/").collect::<Vec<&str>>();
let to_parts = rewrite.to.split("/").collect::<Vec<&str>>();
let path_parts = path.split("/").collect::<Vec<&str>>();
let mut index = 0;
let mut new_path = String::new();
for (from_part, to_part) in from_parts.into_iter().zip(to_parts) {
if from_part == "*" {
new_path += &("/".to_owned() + path_parts[index]);
} else if from_part == "**" {
new_path += &("/".to_owned() + &path_parts[index..].join("/"));
break;
} else {
if from_part != path_parts[index] {
break;
}
new_path += &("/".to_owned() + to_part);
}
index += 1;
}
if index == path_parts.len() {
return Some(new_path);
}
}
None
}
fn handle_serveds<'a>(_req: HttpRequest, new_path: String, _obj: &'a App<'a>, serveds: Vec<(Served, Box<[&'static str]>)>, full_global_root: &str) -> Option<actix_web::HttpResponse> {
let mut path = new_path.as_str();
if path.starts_with("/") {
path = &path[1..];
}
if path.ends_with("/") {
path = &path[..path.len() - 1];
}
for (served, excludes) in serveds.iter() {
let mut http_path = &served.root;
let fighting_with_the_rust_borrow_checker_here = http_path[1..].to_string();
if http_path.starts_with("/") {
http_path = &fighting_with_the_rust_borrow_checker_here;
}
let file_path = &served.path;
let parts = http_path.split("/").collect::<Vec<&str>>();
let mut global_root = full_global_root;
let mut path = path;
for exclude in excludes.iter() {
let mut exclude = exclude.to_string();
if exclude.starts_with("/") {
exclude = exclude[1..].to_string();
}
if exclude.ends_with("/") {
exclude = exclude[..exclude.len() - 1].to_string();
}
let mut matches = false;
for (exclude, part) in exclude.split("/").zip(path.split("/")) {
if exclude == "**" {
matches = true;
break;
}
if exclude == "*" {
continue;
}
if exclude != part {
break;
}
matches = true;
}
if matches {
for template in _obj.response_templates.iter() {
match template {
ResponseTemplate::RestrictedAccess(path) => {
let extension = path::Path::new(path).extension().unwrap();
let data = std::fs::read(path).unwrap();
if extension == "html" {
let mut builder = actix_web::HttpResponse::build(actix_web::http::StatusCode::FORBIDDEN);
builder.content_type("text/html; charset=utf-8");
if data.is_empty() {
return Some(builder.body("Forbidden\nCannot make a request to ".to_owned() + _req.uri().path() + ".\n*DEVELOPER* - Your template is empty."));
} else {
return Some(builder.body(data));
}
}
if data.is_empty() {
return Some(actix_web::HttpResponse::Forbidden().body("Forbidden\nCannot make a request to ".to_owned() + _req.uri().path() + ".\n*DEVELOPER* - Your template is empty."));
} else {
return Some(actix_web::HttpResponse::Forbidden().body(data));
}
}
_ => {}
}
}
return Some(actix_web::HttpResponse::Forbidden().body("Forbidden\nCannot make a request to ".to_owned() + _req.uri().path() + ".\n"));
}
}
if path.starts_with("/") {
path = &path[1..];
}
if global_root.starts_with("/") {
global_root = &global_root[1..];
}
if global_root.ends_with("/") {
global_root = &global_root[..global_root.len() - 1];
}
for part in parts {
if part.starts_with("..") {
for template in _obj.response_templates.iter() {
match template {
ResponseTemplate::InvalidRequest(path) => {
let extension = path::Path::new(path).extension().unwrap();
let data = std::fs::read(path).unwrap();
if extension == "html" {
let mut builder = actix_web::HttpResponse::build(actix_web::http::StatusCode::BAD_REQUEST);
if data.is_empty() {
return Some(builder.body("Bad request: Invalid path\nCannot make a request to ".to_owned() + _req.uri().path() + ".\n*DEVELOPER* - Your template is empty."));
} else {
builder.content_type("text/html; charset=utf-8");
return Some(builder.body(data));
}
}
if data.is_empty() {
return Some(actix_web::HttpResponse::BadRequest().body("Bad request: Invalid path\nCannot make a request to ".to_owned() + _req.uri().path() + ".\n*DEVELOPER* - Your template is empty."));
} else {
return Some(actix_web::HttpResponse::BadRequest().body(data));
}
}
_ => {}
}
}
return Some(actix_web::HttpResponse::BadRequest().body("Bad request: Invalid path\nCannot make a request to ".to_owned() + _req.uri().path() + ".\n"));
}
}
let i_hate_you = path.replace(http_path, "");
path = &i_hate_you;
if path.starts_with(global_root) {
path = &path[global_root.len()..];
}
if served.index_html && path == "" {
path = "index.html";
}
let full_path = format!("{}/{}", file_path, path.to_string());
let file = std::fs::read(full_path.clone());
if file.is_err() {
continue;
}
return Some(actix_web::HttpResponse::Ok()
.content_type(mime_guess::from_path(full_path).first_or_octet_stream().to_string())
.body(file.unwrap()));
}
None
}
#[derive(Debug)]
struct StringError(String);
impl std::fmt::Display for StringError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for StringError {}
fn normalize_path(path: &str) -> &str {
let mut path = path;
if path.starts_with("/") {
path = &path[1..];
}
if path.ends_with("/") {
path = &path[..path.len() - 1];
}
path
}
#[derive(PartialEq, Debug)]
pub enum MiddlewareNext {
Next,
Break
}
#[allow(dead_code)]
impl<'a> App<'a> {
pub fn new(root: (String, Option<Route>)) -> App<'a> {
let mut global_root = root.0.clone();
if global_root.starts_with("/") {
global_root = global_root[1..].to_string();
}
if global_root.ends_with("/") {
global_root = global_root[..global_root.len() - 1].to_string();
}
let temp_global_root = "/".to_owned() + &global_root;
let full_global_root: &'a str = Box::leak(Box::new(temp_global_root));
App {
root,
fragments: Vec::new(),
served: Vec::new(),
rewrites: Vec::new(),
full_global_root,
response_templates: Vec::new(),
middlewares: Vec::new(),
}
}
pub fn fragment(&mut self, fragment: impl Fragment + 'static) -> &mut Self {
self.fragments.push(Box::new(fragment));
self
}
pub fn fragments(&mut self, fragments: Box<[impl Fragment + 'static]>) -> &mut Self {
for fragment in fragments {
self.fragments.push(Box::new(fragment));
}
self
}
pub fn dir(&mut self, served: Served, excludes: Box<[&'static str]>) -> &mut Self {
self.served.push((served, excludes));
self
}
pub fn rewrite(&mut self, rewrite: Rewrite) -> &mut Self {
self.rewrites.push(rewrite);
self
}
pub fn add_response_template(&mut self, template: ResponseTemplate) -> &mut Self {
match template {
ResponseTemplate::RequestVerificationFailed(path) => {
if fs::metadata(path).is_err() {
panic!("Path does not exist");
}
if std::path::Path::new(path).extension().is_none() {
panic!("Path must have an extension of .txt or .html");
} else {
let extension = std::path::Path::new(path).extension().unwrap();
if extension != "txt" && extension != "html" {
panic!("Path must have an extension of .txt or .html");
}
}
}
ResponseTemplate::ResponseVerificationFailed(path) => {
if fs::metadata(path).is_err() {
panic!("Path does not exist");
}
if std::path::Path::new(path).extension().is_none() {
panic!("Path must have an extension of .txt or .html");
} else {
let extension = std::path::Path::new(path).extension().unwrap();
if extension != "txt" && extension != "html" {
panic!("Path must have an extension of .txt or .html");
}
}
}
ResponseTemplate::NotFound(path) => {
if fs::metadata(path).is_err() {
panic!("Path does not exist");
}
if std::path::Path::new(path).extension().is_none() {
panic!("Path must have an extension of .txt or .html");
} else {
let extension = std::path::Path::new(path).extension().unwrap();
if extension != "txt" && extension != "html" {
panic!("Path must have an extension of .txt or .html");
}
}
},
ResponseTemplate::InvalidRequest(path) => {
if fs::metadata(path).is_err() {
panic!("Path does not exist");
}
if std::path::Path::new(path).extension().is_none() {
panic!("Path must have an extension of .txt or .html");
} else {
let extension = std::path::Path::new(path).extension().unwrap();
if extension != "txt" && extension != "html" {
panic!("Path must have an extension of .txt or .html");
}
}
},
ResponseTemplate::RestrictedAccess(path) => {
if fs::metadata(path).is_err() {
panic!("Path does not exist");
}
if std::path::Path::new(path).extension().is_none() {
panic!("Path must have an extension of .txt or .html");
} else {
let extension = std::path::Path::new(path).extension().unwrap();
if extension != "txt" && extension != "html" {
panic!("Path must have an extension of .txt or .html");
}
}
}
}
self.response_templates.push(template);
self
}
pub fn add_response_templates(&mut self, templates: Vec<ResponseTemplate>) -> &mut Self {
for template in templates {
self.add_response_template(template);
}
self
}
pub fn middleware(&mut self, _middleware: impl Fn(&HttpRequest) -> Pin<Box<dyn Future<Output = Result<Response, MiddlewareNext>>>> + 'static) -> &mut Self {
self.middlewares.push(Box::new(_middleware));
self
}
pub fn schema(&mut self) -> AppSchema {
let root_method = &self.root.1;
let root_method_str = match root_method {
Some(method) => match method.method {
Method::Get => "GET",
Method::Post => "POST",
Method::Put => "PUT",
Method::Delete => "DELETE",
Method::Patch => "PATCH",
},
None => "NO_ROOT",
};
let mut base_schema: AppSchema = AppSchema {
root: AppRootSchema {
method: root_method_str.to_string(),
path: self.root.0.clone(),
},
fragments: HashMap::new(),
static_dirs: HashMap::new(),
rewrites: Vec::new(),
templates: HashMap::new(),
};
let mut fragments = HashMap::<String, FragmentSchema>::new();
for fragment in self.fragments.iter() {
let descriptor = fragment.describe();
let base = normalize_path(&self.root.0).to_owned() + "/" + normalize_path(descriptor.root);
let mut routes = Vec::<FragmentRouteSchema>::new();
for route in descriptor.routes.iter() {
let route_descriptor = route;
let route_base = base.to_owned() + "/" + normalize_path(route_descriptor.0);
let method = &route_descriptor.1.method;
let method_str = match method {
Method::Get => "GET",
Method::Post => "POST",
Method::Put => "PUT",
Method::Delete => "DELETE",
Method::Patch => "PATCH",
};
let guard = route_descriptor.1.guard.to_string();
routes.push(FragmentRouteSchema {
method: method_str.to_string(),
guard,
path: route_base,
});
}
let root_fn = &descriptor.root_fn;
let mut root_method_str = "NO_ROOT";
if root_fn.is_some() {
let root_method = root_fn.as_ref().unwrap();
root_method_str = match root_method.method {
Method::Get => "GET",
Method::Post => "POST",
Method::Put => "PUT",
Method::Delete => "DELETE",
Method::Patch => "PATCH",
};
}
fragments.insert(descriptor.root.to_string(), FragmentSchema {
root: AppRootSchema {
method: root_method_str.to_string(),
path: base,
},
routes,
});
}
let mut statics = HashMap::<String, ServedSchema>::new();
for served in self.served.iter() {
let mut excludes_json = Vec::<String>::new();
for exclude in served.1.iter() {
excludes_json.push(exclude.to_string());
}
let served = &served.0;
statics.insert(served.clone().root, ServedSchema {
root: served.root.clone(),
file_path: served.path.clone(),
index_html: served.index_html,
excludes: excludes_json,
});
}
let mut rewrites = Vec::<RewriteSchema>::new();
for rewrite in self.rewrites.iter() {
rewrites.push(RewriteSchema {
from: rewrite.from.clone(),
to: rewrite.to.clone(),
});
}
let mut templates = HashMap::<String, String>::new();
for template in self.response_templates.iter() {
let template_type = match template {
ResponseTemplate::RequestVerificationFailed(_) => "RequestVerificationFailed",
ResponseTemplate::ResponseVerificationFailed(_) => "ResponseVerificationFailed",
ResponseTemplate::NotFound(_) => "NotFound",
ResponseTemplate::InvalidRequest(_) => "InvalidRequest",
ResponseTemplate::RestrictedAccess(_) => "RestrictedAccess",
};
let path = match template {
ResponseTemplate::RequestVerificationFailed(path) => path,
ResponseTemplate::ResponseVerificationFailed(path) => path,
ResponseTemplate::NotFound(path) => path,
ResponseTemplate::InvalidRequest(path) => path,
ResponseTemplate::RestrictedAccess(path) => path,
};
templates.insert(template_type.to_string(), path.to_string());
}
base_schema.fragments = fragments;
base_schema.static_dirs = statics;
base_schema.rewrites = rewrites;
base_schema.templates = templates;
base_schema
}
pub fn schema_str(&mut self) -> String {
let schema = self.schema();
serde_json::to_string_pretty(&schema).unwrap()
}
pub async fn serve(&'static self, port: u16, callback: Pin<Box<dyn Future<Output = ()>>>) -> () {
let app_ = self;
start_guard_event_loop();
let run_inner = async move {
actix_web::HttpServer::new(move || {
let app = actix_web::App::new()
.wrap_fn(move |req, srv| {
let app_ = app_;
let fut = srv.call(req);
async {
let response = fut.await.map_err(|e| actix_web::Error::from(e))?;
let err_template = Err(actix_web::Error::from(Box::new(StringError("Middleware failed".to_string())) as Box<dyn std::error::Error>));
for middleware in app_.middlewares.iter() {
let middleware_response = middleware(&response.request()).await;
if middleware_response.is_err() {
if middleware_response.unwrap_err() == MiddlewareNext::Next {
continue;
} else {
return err_template;
}
}
if middleware_response.is_ok() {
let middleware_response = middleware_response.unwrap();
return Ok(response.map_body(move |head, _body| {
head.status = StatusCode::from_u16(middleware_response.status).unwrap();
let he = head.headers_mut();
for header in middleware_response.headers.iter() {
he.insert(HeaderName::from_bytes(header.0.as_bytes()).unwrap(), HeaderValue::from_str(header.1.as_str()).unwrap());
}
he.insert(HeaderName::from_bytes("content-type".as_bytes()).unwrap(), HeaderValue::from_str(middleware_response.content_type.as_str()).unwrap());
let mw = middleware_response.body.unwrap_or("".to_string());
let mw_bytes = mw.as_bytes().to_vec();
BoxBody::new(mw_bytes)
}));
}
}
Ok(response)
}
})
.service(resource(app_.full_global_root)
.to(move |req: HttpRequest, stream: Payload| async move {
let mut _method = Method::Get;
match req.method().as_str() {
"POST" => _method = Method::Post,
"PUT" => _method = Method::Put,
"DELETE" => _method = Method::Delete,
"PATCH" => _method = Method::Patch,
_ => {}
}
root::root_handle_request(req, stream, Method::Get, app_, &self.response_templates).await
})
)
.service(web::resource("/{tail:.*}")
.to(move |req: HttpRequest, stream: Payload| async move {
let req_clone = req.clone();
let path = req_clone.match_info().get("tail").unwrap();
let new_path = match_rewrite(path, &app_.rewrites);
let req_clone = req.clone();
let np_clone = new_path.clone();
let served_response = handle_serveds(req, new_path.unwrap_or(path.to_string()), app_, app_.served.clone(), app_.full_global_root);
if served_response.is_some() {
return served_response.unwrap();
}
let mut _method = Method::Get;
match req_clone.method().as_str() {
"POST" => _method = Method::Post,
"PUT" => _method = Method::Put,
"DELETE" => _method = Method::Delete,
"PATCH" => _method = Method::Patch,
_ => {}
}
frag::frag_handle_request(req_clone, stream, np_clone.unwrap_or(path.to_string()), _method, app_, app_.full_global_root, &self.response_templates).await
})
.default_service(web::route().to(move |req: HttpRequest| async move {
for template in self.response_templates.iter() {
match template {
ResponseTemplate::NotFound(path) => {
let extension = path::Path::new(path).extension().unwrap();
let data = std::fs::read(path).unwrap();
if extension == "html" {
let mut builder = actix_web::HttpResponse::build(actix_web::http::StatusCode::NOT_FOUND);
if data.is_empty() {
return builder.body("Route not found\nCannot make a request to ".to_owned() + req.uri().path() + ".\n*DEVELOPER* - Your template is empty.");
} else {
builder.content_type("text/html; charset=utf-8");
return builder.body(data);
}
}
if data.is_empty() {
return actix_web::HttpResponse::NotFound().body("Route not found\nCannot make a request to ".to_owned() + req.uri().path() + ".\n*DEVELOPER* - Your template is empty.");
} else {
return actix_web::HttpResponse::NotFound().body(data);
}
}
_ => {}
}
}
actix_web::HttpResponse::NotFound().body("Route not found\nCannot make a request to ".to_owned() + req.uri().path() + ".\n")
}))
);
app
})
.bind(("127.0.0.1", port))
.unwrap()
.run()
.await
.unwrap();
()
};
tokio::join!(run_inner, callback);
}
}