1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! 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;

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) -> String {
        let mut body = String::with_capacity(150);

        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");

        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 _ = write!(body, "<li><a href=\"{}\">{}",
                                 utf8_percent_encode(&entry.path().to_string_lossy(), DEFAULT_ENCODE_SET),
                                 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
    }
}

///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)
    }

    ///Returns name of index file to show.
    ///
    ///`path` is always 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
    ///
    ///By default returns `false`
    fn handle_directory(&self, _path: &Path) -> bool {
        false
    }

    ///Describes how to configure thread pool.
    ///
    ///By default sets prefix `http-fs` and leaves everything else by default
    fn thread_pool_builder(builder: &mut tokio_threadpool::Builder) -> &mut tokio_threadpool::Builder {
        builder.name_prefix("http-fs")
    }
}

///Default configuration
pub struct DefaultConfig;
impl FileServeConfig for DefaultConfig {}
impl DirectoryListingConfig for DefaultConfig {}
impl StaticFileConfig for DefaultConfig {
    type FileService = Self;
    type DirService = Self;
}