use percent_encoding::utf8_percent_encode;
use crate::utils::PATH_ENCODE_SET;
use std::fs;
use std::path::Path;
use core::marker;
use core::fmt::{self, Write};
use core::future::{self, Future};
use crate::headers::cd::DispositionType;
use crate::file::FileReadResult;
pub const DEFAULT_SERVE_DIR: &'static str = ".";
pub trait FsTaskSpawner: marker::Send + Clone + Unpin {
type SpawnError: std::error::Error + fmt::Debug + fmt::Display + Send + Sync + 'static;
type FileReadFut: Future<Output = Result<FileReadResult, Self::SpawnError>> + Unpin;
fn spawn_file_read<F: FnOnce() -> FileReadResult + Send + 'static>(fut: F) -> Self::FileReadFut;
}
pub trait FileServeConfig: marker::Send + Unpin {
fn max_buffer_size() -> u64 {
65_536
}
fn content_disposition_map(typ: mime::Name) -> DispositionType {
match typ {
mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
_ => DispositionType::Attachment,
}
}
fn is_use_etag(_path: &Path) -> bool {
true
}
fn is_use_last_modifier(_path: &Path) -> bool {
true
}
}
pub trait DirectoryListingConfig {
fn create_body(base: &Path, path: &Path, dir: fs::ReadDir) -> bytes::Bytes {
let mut body = crate::utils::BytesWriter::with_capacity(256);
let _ = write!(body, "<html>\n");
let _ = write!(body, " <head>\n");
let _ = write!(body, " <title>Index of {}/</title>\n", path.display());
let _ = write!(body, " </head>\n");
let _ = write!(body, " <body>\n");
let _ = write!(body, " <h1>Index of {}/</h1>\n", path.display());
let _ = write!(body, " <ul>\n");
let _ = write!(body, "<li><a href=\"/{}\">.</a></li>\n", utf8_percent_encode(&path.to_string_lossy(), PATH_ENCODE_SET));
if let Some(parent) = path.parent() {
let _ = write!(body, "<li><a href=\"/{}\">..</a></li>\n", utf8_percent_encode(&parent.to_string_lossy(), PATH_ENCODE_SET));
}
for entry in dir.filter_map(|entry| entry.ok()) {
if entry.file_name().to_str().map(|entry| entry.starts_with('.')).unwrap_or(false) {
continue;
}
let is_dir = match entry.metadata().map(|meta| meta.file_type()) {
Ok(meta) => if !meta.is_dir() && !meta.is_file() && !meta.is_symlink() {
continue;
} else {
meta.is_dir()
},
Err(_) => continue,
};
let entry_path = entry.path();
let entry_path = match entry_path.strip_prefix(base) {
Ok(res) => res,
Err(_) => &entry_path,
};
let _ = write!(body, "<li><a href=\"");
for component in entry_path.components().map(|component| component.as_os_str()) {
let _ = write!(body, "/{}", utf8_percent_encode(&component.to_string_lossy(), PATH_ENCODE_SET));
}
let _ = write!(body, "\">{}", v_htmlescape::escape(&entry.file_name().to_string_lossy()));
if is_dir {
let _ = write!(body, "/");
}
let _ = write!(body, "</a></li>\n");
}
let _ = write!(body, " </ul>\n");
let _ = write!(body, " </body>\n");
let _ = write!(body, "</html>\n");
body.freeze()
}
}
pub trait StaticFileConfig {
type FileService: FileServeConfig + 'static;
type DirService: DirectoryListingConfig;
fn is_method_allowed(method: &http::Method) -> bool {
match method {
&http::Method::GET | &http::Method::HEAD => true,
_ => false,
}
}
fn serve_dir(&self) -> &Path {
Path::new(DEFAULT_SERVE_DIR)
}
fn router_prefix(&self) -> &str {
"/"
}
fn index_file(&self, _path: &Path) -> Option<&Path> {
None
}
fn handle_directory(&self, _path: &Path) -> bool {
false
}
fn handle_not_found(&self, _path: &Path, _out_headers: &mut http::HeaderMap) -> (http::StatusCode, bytes::Bytes) {
(http::StatusCode::NOT_FOUND, bytes::Bytes::new())
}
}
#[derive(Clone, Copy)]
pub struct DefaultConfig;
impl FileServeConfig for DefaultConfig {}
impl DirectoryListingConfig for DefaultConfig {}
impl StaticFileConfig for DefaultConfig {
type FileService = Self;
type DirService = Self;
}
#[derive(Clone, Copy)]
pub struct DummyWorker;
impl FsTaskSpawner for DummyWorker {
type SpawnError = core::convert::Infallible;
type FileReadFut = future::Ready<Result<FileReadResult, Self::SpawnError>>;
#[cold]
#[inline(never)]
fn spawn_file_read<F: FnOnce() -> FileReadResult + Send + 'static>(_: F) -> Self::FileReadFut {
unimplemented!()
}
}
#[cfg(feature = "tokio")]
#[derive(Clone, Copy)]
pub struct TokioWorker;
#[cfg(feature = "tokio")]
impl FsTaskSpawner for TokioWorker {
type SpawnError = tokio::task::JoinError;
type FileReadFut = tokio::task::JoinHandle<FileReadResult>;
fn spawn_file_read<F: FnOnce() -> FileReadResult + Send + 'static>(fut: F) -> Self::FileReadFut {
tokio::task::spawn_blocking(fut)
}
}