http-fs 0.5.0

HTTP File Service library
Documentation
//! Configuration module

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;

///Default serve directory
pub const DEFAULT_SERVE_DIR: &'static str = ".";

///Describes how to serve file
pub trait FileServeConfig: std::marker::Send {
    ///Returns maximum size for buffer to read file.
    ///
    ///By default 64kb
    fn max_buffer_size() -> u64 {
        65_536
    }

    ///Describes mapping for mime type to content disposition header
    ///
    ///By default `IMAGE`, `TEXT` and `VIDEO` are mapped to Inline.
    ///Others are mapped to Attachment
    fn content_disposition_map(typ: mime::Name) -> DispositionType {
        match typ {
            mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline,
            _ => DispositionType::Attachment,
        }
    }

    ///Specifies whether `ETag` should be used for caching
    ///
    ///Provides path to the file, relative to File Service directory
    ///
    ///By default it is true
    fn is_use_etag(_path: &Path) -> bool {
        true
    }

    ///Specifies whether `Last-Modified` should be used for caching
    ///
    ///Provides path to the file, relative to File Service directory
    ///
    ///By default it is true
    fn is_use_last_modifier(_path: &Path) -> bool {
        true
    }
}

///Describes how to serve directory
pub trait DirectoryListingConfig {
    ///Describes how to create HTML text for Directory listing
    ///
    ///`base` is root directory for file service.
    ///`path` is relative to `base` path to directory.
    ///`dir` is result of `fs::read_dir`
    ///
    ///Default implementation provides trivial listing
    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()
    }
}

///Configuration description
pub trait StaticFileConfig {
    ///File serve configuration
    type FileService: FileServeConfig + 'static;
    ///Directory serve configuration
    type DirService: DirectoryListingConfig;

    ///Returns whether specified method is allowed for use.
    ///By default allows `HEAD` and `GET`
    fn is_method_allowed(method: &http::Method) -> bool {
        match method {
            &http::Method::GET | &http::Method::HEAD => true,
            _ => false,
        }
    }

    ///Returns directory from where to serve files.
    ///
    ///By default returns `.`
    fn serve_dir(&self) -> &Path {
        Path::new(DEFAULT_SERVE_DIR)
    }

    ///Specifies router prefix.
    ///
    ///To be used by frameworks such as Actix
    ///
    ///Defaults to `/`
    fn router_prefix(&self) -> &str {
        "/"
    }

    ///Returns name of index file to show.
    ///
    ///`path` points to directory relative to `StaticFileConfig::serve_dir`.
    ///
    ///By default returns `None`
    fn index_file(&self, _path: &Path) -> Option<&Path> {
        None
    }

    ///Returns whether directory should be listed on access
    ///
    ///`path` points to directory relative to `StaticFileConfig::serve_dir`.
    ///
    ///By default returns `false`
    fn handle_directory(&self, _path: &Path) -> bool {
        false
    }

    ///Handles entry that hasn't been found.
    ///
    ///`path` points to entry relative to `StaticFileConfig::serve_dir`.
    ///
    ///By default prepares empty `NotFound` response with empty body
    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))
    }

    ///Describes how to get instance of `threadpool::ThreadPool`
    ///
    ///By default sets prefix `http-fs` and leaves everything else by default
    fn thread_pool_builder() -> threadpool::ThreadPool {
        threadpool::Builder::new().thread_name("http-fs".to_owned()).build()
    }
}

#[derive(Clone)]
///Default configuration
pub struct DefaultConfig;
impl FileServeConfig for DefaultConfig {}
impl DirectoryListingConfig for DefaultConfig {}
impl StaticFileConfig for DefaultConfig {
    type FileService = Self;
    type DirService = Self;
}