use crate::app::{
PathAwareRequestHandler, RequestHandler, StatelessRequestHandler, WebsocketHandler,
};
use crate::http::cors::Cors;
use crate::krauss;
use crate::percent::PercentDecode;
use std::fs::metadata;
use std::path::PathBuf;
pub struct SubApp<State> {
pub host: String,
pub routes: Vec<RouteHandler<State>>,
pub websocket_routes: Vec<WebsocketRouteHandler<State>>,
pub cors: Option<Cors>,
}
pub struct RouteHandler<State> {
pub route: String,
pub handler: Box<dyn RequestHandler<State>>,
pub cors: Cors,
}
pub struct WebsocketRouteHandler<State> {
pub route: String,
pub handler: Box<dyn WebsocketHandler<State>>,
}
impl<State> Default for SubApp<State> {
fn default() -> Self {
SubApp {
host: "*".to_string(),
routes: Vec::new(),
websocket_routes: Vec::new(),
cors: None,
}
}
}
impl<State> SubApp<State> {
pub fn new() -> Self {
SubApp::default()
}
pub fn with_route<T>(mut self, route: &str, handler: T) -> Self
where
T: RequestHandler<State> + 'static,
{
self.routes.push(RouteHandler {
route: route.to_string(),
handler: Box::new(handler),
cors: self.cors.as_ref().map_or(Cors::default(), |c| c.clone()),
});
self
}
pub fn with_stateless_route<T>(mut self, route: &str, handler: T) -> Self
where
T: StatelessRequestHandler<State> + 'static,
{
self.routes.push(RouteHandler {
route: route.to_string(),
handler: Box::new(move |request, _| handler.serve(request)),
cors: self.cors.as_ref().map_or(Cors::default(), |c| c.clone()),
});
self
}
pub fn with_path_aware_route<T>(mut self, route: &'static str, handler: T) -> Self
where
T: PathAwareRequestHandler<State> + 'static,
{
self.routes.push(RouteHandler {
route: route.to_string(),
handler: Box::new(move |request, state| handler.serve(request, state, route)),
cors: self.cors.as_ref().map_or(Cors::default(), |c| c.clone()),
});
self
}
pub fn with_websocket_route<T>(mut self, route: &str, handler: T) -> Self
where
T: WebsocketHandler<State> + 'static,
{
self.websocket_routes.push(WebsocketRouteHandler {
route: route.to_string(),
handler: Box::new(handler),
});
self
}
pub fn with_cors(mut self, cors: Cors) -> Self {
self.cors = Some(cors.clone());
self.routes.iter_mut().for_each(|route| {
route.cors = cors.clone();
});
self
}
pub fn with_cors_config(mut self, route: &str, cors: Cors) -> Self {
self.routes.iter_mut().for_each(|r| {
if r.route == route {
r.cors = cors.clone();
}
});
self
}
}
pub trait Route {
fn route_matches(&self, route: &str) -> bool;
}
impl Route for String {
fn route_matches(&self, route: &str) -> bool {
krauss::wildcard_match(self, route)
}
}
pub enum LocatedPath {
Directory,
File(PathBuf),
}
pub fn try_find_path(
directory: &str,
request_path: &str,
index_files: &[&str],
) -> Option<LocatedPath> {
let request_path = String::from_utf8(request_path.percent_decode()?).ok()?;
if request_path.contains("..") || request_path.contains(':') {
return None;
}
let request_path = request_path.trim_start_matches('/');
let directory = directory.trim_end_matches('/');
if request_path.ends_with('/') || request_path.is_empty() {
for filename in index_files {
let path = format!("{}/{}{}", directory, request_path, *filename);
if let Ok(meta) = metadata(&path) {
if meta.is_file() {
return Some(LocatedPath::File(
PathBuf::from(path).canonicalize().unwrap(),
));
}
}
}
} else {
let path = format!("{}/{}", directory, request_path);
if let Ok(meta) = metadata(&path) {
if meta.is_file() {
return Some(LocatedPath::File(
PathBuf::from(path).canonicalize().unwrap(),
));
} else if meta.is_dir() {
return Some(LocatedPath::Directory);
}
}
}
None
}