vacuna 0.4.2

Simple web server for static files
Documentation
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_fmt 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,

    pub(crate) pre_compressed: Option<CompressedFileTypes>,
}

impl Directory {
    /// Create a new directory
    pub fn new(base: PathBuf, path: PathBuf, pre_compressed: Option<CompressedFileTypes>) -> Directory {
        Directory {
            base,
            path,
            pre_compressed,
        }
    }

    /// Is this entry visible from this directory?
    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 file is a directory, add '/' to the end of the name
            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),
    ))
}