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
//!# http-fs
//!
//!## Features
//!
//!- `hyper` - Enables `hyper` integration.
//!
//!## Usage
//!
//!```rust
//!use http_fs::config::{self, StaticFileConfig};
//!use http_fs::{StaticFiles};
//!
//!use std::path::Path;
//!
//!pub struct DirectoryConfig;
//!impl StaticFileConfig for DirectoryConfig {
//!    type FileService = config::DefaultConfig;
//!    type DirService = config::DefaultConfig;
//!
//!    fn handle_directory(&self, _path: &Path) -> bool {
//!        true
//!    }
//!}
//!
//!fn main() {
//!    let static_files = StaticFiles::new(DirectoryConfig);
//!}
//!```

#![warn(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]

pub extern crate threadpool;
pub extern crate http;
pub extern crate etag;
pub extern crate httpdate;

pub mod config;
pub mod headers;
pub mod file;
pub mod utils;
pub mod adaptors;

pub use config::{FileServeConfig, DirectoryListingConfig, StaticFileConfig};

use std::io;
use std::fs;
use std::path::{PathBuf, Path};

#[derive(Debug)]
///Entry's in `fs`
pub enum ServeEntry {
    ///Entry is not found
    NotFound,
    ///Error when looking up entry
    ///
    ///Likely because file either doesn't exist or you lacks permissions
    IoError(io::Error),
    ///File entry is found
    File(fs::File, fs::Metadata, PathBuf),
    ///Directory entry is found
    Directory(PathBuf, fs::ReadDir),
}

/// Static files service
pub struct StaticFiles<C = config::DefaultConfig> {
    workers: threadpool::ThreadPool,
    config: C,
}

impl<C: Clone> Clone for StaticFiles<C> {
    fn clone(&self) -> StaticFiles<C> {
        Self {
            workers: self.workers.clone(),
            config: self.config.clone(),
        }
    }
}

impl<C: StaticFileConfig> StaticFiles<C> {
    ///Creates new instance with provided config
    pub fn new(config: C) -> Self {
        Self {
            workers: C::thread_pool_builder(),
            config,
        }
    }

    ///Returns reference to thread pool
    pub fn workers(&self) -> &threadpool::ThreadPool {
        &self.workers
    }

    ///Serves file
    pub fn serve(&self, path: &Path) -> ServeEntry {
        let mut full_path = self.config.serve_dir().join(path);

        let mut meta = match full_path.metadata() {
            Ok(meta) => meta,
            Err(_) => return ServeEntry::NotFound,
        };

        if meta.is_dir() {
            if let Some(name) = self.config.index_file(path) {
                full_path = full_path.join(name);
                meta = match full_path.metadata() {
                    Ok(meta) => meta,
                    Err(_) => return ServeEntry::NotFound,
                };
            } else if self.config.handle_directory(path) {
                return match full_path.read_dir() {
                    Ok(dir) => ServeEntry::Directory(path.to_path_buf(), dir),
                    Err(error) => ServeEntry::IoError(error),
                }
            } else {
                return ServeEntry::NotFound
            }
        }

        match fs::File::open(&full_path) {
            Ok(file) => ServeEntry::File(file, meta, full_path),
            Err(error) => ServeEntry::IoError(error),
        }
    }

    ///Handles not found directory
    pub fn handle_not_found(&self, path: &Path, out_headers: &mut http::HeaderMap) -> (http::StatusCode, bytes::Bytes) {
        self.config.handle_not_found(path, out_headers)
    }

    ///Serve directory routine
    pub fn list_dir(&self, path: &Path, dir: fs::ReadDir) -> bytes::Bytes {
        C::DirService::create_body(self.config.serve_dir(), path, dir)
    }

    ///Serves file routine
    pub fn serve_file(&self, path: &Path, file: fs::File, meta: fs::Metadata, method: http::Method, headers: &http::HeaderMap, out_headers: &mut http::HeaderMap) -> (http::StatusCode, Option<file::ChunkedReadFile<C::FileService>>) {
        let file_name = match path.file_name().and_then(|file_name| file_name.to_str()) {
            Some(file_name) => file_name,
            None => return (http::StatusCode::NOT_FOUND, None)
        };

        file::ServeFile::<C::FileService>::from_parts_with_cfg(file_name, file, meta).prepare(path, method, headers, out_headers, self.workers())
    }
}

impl Default for StaticFiles<config::DefaultConfig> {
    #[inline]
    fn default() -> Self {
        Self::new(config::DefaultConfig)
    }
}