use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf}; use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse}; use percent_encoding::{utf8_percent_encode, CONTROLS}; use v_htmlescape::escape as escape_html_entity; /// A directory; responds with the generated directory listing. #[derive(Debug)] pub struct Directory { /// Base directory. pub base: PathBuf, /// Path of subdirectory to generate listing for. pub path: PathBuf, } impl Directory { /// Create a new directory pub fn new(base: PathBuf, path: PathBuf) -> Directory { Directory { base, path } } /// Is this entry visible from this directory? pub fn is_visible(&self, entry: &io::Result) -> 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; // show file url as relative to static path macro_rules! encode_file_url { ($path:ident) => { utf8_percent_encode(&$path, CONTROLS) }; } // " -- " & -- & ' -- ' < -- < > -- > / -- / macro_rules! encode_file_name { ($entry:ident) => { escape_html_entity(&$entry.file_name().to_string_lossy()) }; } pub(crate) fn directory_listing( dir: &Directory, req: &HttpRequest, ) -> Result { let index_of = format!("Index of {}", req.path()); let mut body = String::new(); let base = Path::new(req.path()); for entry in dir.path.read_dir()? { if dir.is_visible(&entry) { let entry = entry.unwrap(); let 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 file is a directory, add '/' to the end of the name if let Ok(metadata) = entry.metadata() { if metadata.is_dir() { let _ = write!( body, "
  • {}/
  • ", encode_file_url!(p), encode_file_name!(entry), ); } else { let _ = write!( body, "
  • {}
  • ", encode_file_url!(p), encode_file_name!(entry), ); } } else { continue; } } } let html = format!( "\ {}\

    {}

    \
      \ {}\
    \n", index_of, index_of, body ); Ok(ServiceResponse::new( req.clone(), HttpResponse::Ok() .content_type("text/html; charset=utf-8") .body(html), )) }