use std::borrow::Cow;
use std::path::{Path, PathBuf};
use crate::http::{ext::IntoOwned, HeaderMap};
use crate::response::Redirect;
use crate::Request;
pub trait Rewriter: Send + Sync + 'static {
fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, req: &'r Request<'_>) -> Option<Rewrite<'r>>;
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Rewrite<'r> {
File(File<'r>),
Redirect(Redirect),
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct File<'r> {
pub path: Cow<'r, Path>,
pub headers: HeaderMap<'r>,
}
impl<'r> File<'r> {
pub fn new(path: impl Into<Cow<'r, Path>>) -> Self {
Self {
path: path.into(),
headers: HeaderMap::new(),
}
}
pub fn checked<P: AsRef<Path>>(path: P) -> Self {
let path = path.as_ref();
if !path.exists() {
let path = path.display();
error!(%path, "FileServer path does not exist.\n\
Panicking to prevent inevitable handler error.");
panic!("missing file {}: refusing to continue", path);
}
Self::new(path.to_path_buf())
}
pub fn map_path<F, P>(self, f: F) -> Self
where
F: FnOnce(Cow<'r, Path>) -> P,
P: Into<Cow<'r, Path>>,
{
Self {
path: f(self.path).into(),
headers: self.headers,
}
}
pub fn is_hidden(&self) -> bool {
self.path
.iter()
.any(|s| s.as_encoded_bytes().starts_with(b"."))
}
pub fn is_visible(&self) -> bool {
!self.is_hidden()
}
}
pub struct Prefix(PathBuf);
impl Prefix {
pub fn checked<P: AsRef<Path>>(path: P) -> Self {
let path = path.as_ref();
if !path.is_dir() {
let path = path.display();
error!(%path, "FileServer path is not a directory.");
warn!("Aborting early to prevent inevitable handler error.");
panic!("invalid directory: refusing to continue");
}
Self(path.to_path_buf())
}
pub fn unchecked<P: AsRef<Path>>(path: P) -> Self {
Self(path.as_ref().to_path_buf())
}
}
impl Rewriter for Prefix {
fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
opt.map(|r| match r {
Rewrite::File(f) => Rewrite::File(f.map_path(|p| self.0.join(p))),
Rewrite::Redirect(r) => Rewrite::Redirect(r),
})
}
}
impl Rewriter for PathBuf {
fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
Some(Rewrite::File(File::new(self.clone())))
}
}
pub struct TrailingDirs;
impl Rewriter for TrailingDirs {
fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, req: &Request<'_>) -> Option<Rewrite<'r>> {
if let Some(Rewrite::File(f)) = &opt {
if !req.uri().path().ends_with('/') && f.path.is_dir() {
let uri = req.uri().clone().into_owned();
let uri = uri.map_path(|p| format!("{p}/")).unwrap();
return Some(Rewrite::Redirect(Redirect::temporary(uri)));
}
}
opt
}
}
pub struct DirIndex {
path: PathBuf,
check: bool,
}
impl DirIndex {
pub fn unconditional(path: impl AsRef<Path>) -> Self {
Self {
path: path.as_ref().to_path_buf(),
check: false,
}
}
pub fn if_exists(path: impl AsRef<Path>) -> Self {
Self {
path: path.as_ref().to_path_buf(),
check: true,
}
}
}
impl Rewriter for DirIndex {
fn rewrite<'r>(&self, opt: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
match opt? {
Rewrite::File(f) if f.path.is_dir() => {
let candidate = f.path.join(&self.path);
if self.check && !candidate.is_file() {
return Some(Rewrite::File(f));
}
Some(Rewrite::File(f.map_path(|_| candidate)))
}
r => Some(r),
}
}
}
impl<'r> From<File<'r>> for Rewrite<'r> {
fn from(value: File<'r>) -> Self {
Self::File(value)
}
}
impl<'r> From<Redirect> for Rewrite<'r> {
fn from(value: Redirect) -> Self {
Self::Redirect(value)
}
}
impl<F: Send + Sync + 'static> Rewriter for F
where
F: for<'r> Fn(Option<Rewrite<'r>>, &Request<'_>) -> Option<Rewrite<'r>>,
{
fn rewrite<'r>(&self, f: Option<Rewrite<'r>>, r: &Request<'_>) -> Option<Rewrite<'r>> {
self(f, r)
}
}
impl Rewriter for Rewrite<'static> {
fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
Some(self.clone())
}
}
impl Rewriter for File<'static> {
fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
Some(Rewrite::File(self.clone()))
}
}
impl Rewriter for Redirect {
fn rewrite<'r>(&self, _: Option<Rewrite<'r>>, _: &Request<'_>) -> Option<Rewrite<'r>> {
Some(Rewrite::Redirect(self.clone()))
}
}