use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use std::fs;
use std::path::Path;
use std::fmt::Write;
use crate::headers::cd::DispositionType;
pub const DEFAULT_SERVE_DIR: &'static str = ".";
pub trait FileServeConfig: std::marker::Send {
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(), DEFAULT_ENCODE_SET));
if let Some(parent) = path.parent() {
let _ = write!(body, "<li><a href=\"/{}\">..</a></li>\n", utf8_percent_encode(&parent.to_string_lossy(), DEFAULT_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(), DEFAULT_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::with_capacity(0))
}
fn thread_pool_builder() -> threadpool::ThreadPool {
threadpool::Builder::new().thread_name("http-fs".to_owned()).build()
}
}
#[derive(Clone)]
pub struct DefaultConfig;
impl FileServeConfig for DefaultConfig {}
impl DirectoryListingConfig for DefaultConfig {}
impl StaticFileConfig for DefaultConfig {
type FileService = Self;
type DirService = Self;
}