use crate::HttpHeaderName;
use crate::ResponseBody;
use crate::{Response, StatusCode};
use crate::MimeType;
use crate::RequestContext;
use crate::TiiResult;
use std::fs::{metadata, File};
use std::io::ErrorKind;
use std::path::PathBuf;
const INDEX_FILES: [&str; 2] = ["index.html", "index.htm"];
pub enum LocatedPath {
Directory,
File(PathBuf),
}
fn try_file_open(path: &PathBuf) -> TiiResult<Response> {
let mime = MimeType::from_extension(
path.extension().map(|a| a.to_string_lossy().to_string()).unwrap_or("".to_string()).as_str(),
);
Ok(File::open(path).and_then(ResponseBody::from_file).map(|a| Response::ok(a, mime)).or_else(
|e| {
if e.kind() == ErrorKind::NotFound {
Ok(Response::not_found_no_body())
} else {
Err(e)
}
},
)?)
}
pub fn serve_file(file_path: &'static str) -> impl Fn(&RequestContext) -> TiiResult<Response> {
let path_buf = PathBuf::from(file_path);
move |_| try_file_open(&path_buf)
}
pub fn serve_as_file_path(
directory_path: &'static str,
) -> impl Fn(&RequestContext) -> TiiResult<Response> {
move |request: &RequestContext| {
let directory_path = directory_path.strip_suffix('/').unwrap_or(directory_path);
let file_path = request.get_path().strip_prefix('/').unwrap_or(request.get_path());
let path = format!("{directory_path}/{file_path}");
let path_buf = PathBuf::from(path);
try_file_open(&path_buf)
}
}
pub fn serve_dir(directory_path: &'static str) -> impl Fn(&RequestContext) -> TiiResult<Response> {
move |request: &RequestContext| {
let route = request.routed_path();
let route_without_wildcard = route.strip_suffix('*').unwrap_or(route);
let uri_without_route =
request.get_path().strip_prefix(route_without_wildcard).unwrap_or(request.routed_path());
let located = try_find_path(directory_path, uri_without_route, &INDEX_FILES);
if let Some(located) = located {
match located {
LocatedPath::Directory => Ok(
Response::new(StatusCode::MovedPermanently)
.with_header(HttpHeaderName::Location, format!("{}/", &request.get_path()))?,
),
LocatedPath::File(path) => try_file_open(&path),
}
} else {
Ok(Response::new(StatusCode::NotFound))
}
}
}
fn try_find_path(directory: &str, request_path: &str, index_files: &[&str]) -> Option<LocatedPath> {
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().ok()?));
}
}
}
} 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().ok()?));
} else if meta.is_dir() {
return Some(LocatedPath::Directory);
}
}
}
None
}
pub fn redirect(location: &'static str) -> impl Fn(&RequestContext) -> TiiResult<Response> {
move |_| Ok(Response::permanent_redirect_no_body(location))
}