use crate::files::compressed_files::CompressedFileTypes;
use actix_web::{HttpRequest, HttpResponse, dev::ServiceResponse};
use percent_encoding::{CONTROLS, utf8_percent_encode};
use std::{
fmt::Write,
fs::DirEntry,
io,
path::{Path, PathBuf},
};
use tracing::trace;
use v_htmlescape::escape as escape_html_entity;
#[derive(Debug)]
pub struct Directory {
pub base: PathBuf,
pub path: PathBuf,
pub(crate) pre_compressed: Option<CompressedFileTypes>,
}
impl Directory {
pub fn new(base: PathBuf, path: PathBuf, pre_compressed: Option<CompressedFileTypes>) -> Directory {
Directory {
base,
path,
pre_compressed,
}
}
pub fn is_visible(&self, entry: &io::Result<DirEntry>) -> bool {
if let Ok(ref entry) = *entry {
if let Some(name) = entry.file_name().to_str() {
if name.starts_with('.') {
return false;
}
}
if let Ok(ref md) = entry.metadata() {
let ft = md.file_type();
return ft.is_dir() || ft.is_file() || ft.is_symlink();
}
}
false
}
}
pub(crate) type DirectoryRenderer = dyn Fn(&Directory, &HttpRequest) -> Result<ServiceResponse, io::Error>;
pub(crate) fn directory_listing(dir: &Directory, req: &HttpRequest) -> Result<ServiceResponse, io::Error> {
let index_of = format!("Index of {}", req.path());
let mut body = String::new();
let base = Path::new(req.path());
let local_path = dir.base.clone();
let local_path = local_path.join(&dir.path);
for entry in local_path.read_dir()? {
if dir.is_visible(&entry) {
let entry = entry.unwrap();
let mut p = match entry.path().strip_prefix(&dir.path) {
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"),
Ok(p) => base.join(p).to_string_lossy().into_owned(),
Err(_) => continue,
};
if let Ok(metadata) = entry.metadata() {
if metadata.is_dir() {
let _ = write!(
body,
"<li><a href=\"{}\">{}/</a></li>",
utf8_percent_encode(&p, CONTROLS),
escape_html_entity(&entry.file_name().to_string_lossy())
);
} else {
let entry_name = entry.file_name();
let mut file_name = entry_name.to_string_lossy().to_string();
if let Some(pre_compressed) = dir.pre_compressed.as_ref() {
trace!("checking file: {} ", file_name);
for (_, suffix) in pre_compressed.iter() {
trace!("checking for suffix: {}", suffix);
if file_name.ends_with(suffix) {
p = p.trim_end_matches(&file_name).to_string();
file_name = file_name.trim_end_matches(suffix).to_string();
p.push_str(&file_name);
trace!("found: {} with suffix {}", file_name, suffix);
break;
}
}
}
let _ = write!(
body,
"<li><a href=\"{}\">{}</a></li>",
utf8_percent_encode(&p, CONTROLS),
escape_html_entity(&file_name)
);
}
} else {
continue;
}
}
}
let html = format!(
"<html>\
<head><title>Vacuna - {}</title></head>\
<body><h1>{}</h1>\
<ul>\
{}\
</ul><p>File listen provided by Vacuna</p></body>\n</html>",
index_of, index_of, body
);
Ok(ServiceResponse::new(
req.clone(),
HttpResponse::Ok().content_type("text/html; charset=utf-8").body(html),
))
}