index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2021-12-10 22:54:16.0 +00:00:00
committer Jose Quintana <joseluisquintana20@gmail.com> 2021-12-10 22:54:16.0 +00:00:00
commit
4ba5a763fc52035feb20616c15f0baa81030bee1 [patch]
tree
94f743001ee4f9a3ae9c34546bdd18e9bd57727a
parent
1231b50c207c55ce97a8346b3329ff680eabb868
download
4ba5a763fc52035feb20616c15f0baa81030bee1.tar.gz

feat: ordering support for name, modified, filesize (asc, desc)



Diff

 Cargo.lock                                           |  17 +++-
 Cargo.toml                                           |   1 +-
 docs/content/configuration/command-line-arguments.md |   9 +-
 docs/content/configuration/environment-variables.md  |   3 +-
 docs/content/examples/directory-listing.md           |  42 +++++-
 src/config.rs                                        |   9 +-
 src/handler.rs                                       |  18 ++-
 src/server.rs                                        |   5 +-
 src/static_files.rs                                  | 132 +++++++++++++++++---
 tests/dir_listing.rs                                 |  12 +-
 tests/static_files.rs                                | 107 +++++++++++++---
 11 files changed, 309 insertions(+), 46 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 6aa6d86..bca6d1a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -224,6 +224,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"

[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
 "matches",
 "percent-encoding",
]

[[package]]
name = "fs_extra"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -509,6 +519,12 @@ dependencies = [
]

[[package]]
name = "matches"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"

[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -801,6 +817,7 @@ dependencies = [
 "async-compression",
 "bcrypt",
 "bytes",
 "form_urlencoded",
 "futures-util",
 "headers",
 "http",
diff --git a/Cargo.toml b/Cargo.toml
index 24d7c7f..23dca1d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -49,6 +49,7 @@ tokio-rustls = { version = "0.22" }
tokio-util = { version = "0.6", default-features = false, features = ["io"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }
tracing-subscriber = { version = "0.3", features = ["smallvec", "fmt", "ansi", "tracing-log", "std"] }
form_urlencoded = "1.0"

[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
version = "0.3"
diff --git a/docs/content/configuration/command-line-arguments.md b/docs/content/configuration/command-line-arguments.md
index 08c37f4..a5ae195 100644
--- a/docs/content/configuration/command-line-arguments.md
+++ b/docs/content/configuration/command-line-arguments.md
@@ -10,7 +10,7 @@ The server can be configured via the following command-line arguments.
```
$ static-web-server -h

static-web-server 2.2.0
static-web-server 2.3.0
Jose Quintana <https://git.io/joseluisq>
A blazing fast and asynchronous web server for static files-serving.

@@ -37,6 +37,11 @@ OPTIONS:
    -z, --directory-listing <directory-listing>
            Enable directory listing for all requests ending with the slash character (‘/’) [env:
            SERVER_DIRECTORY_LISTING=]  [default: false]
        --directory-listing-order <directory-listing-order>
            Specify a default code number to order directory listing entries per `Name`, `Last modified` or `Size`
            attributes (columns). Code numbers supported: 0 (Name asc), 1 (Name desc), 2 (Last modified asc), 3 (Last
            modified desc), 4 (Size asc), 5 (Size desc). Default 6 (unordered) [env: SERVER_DIRECTORY_LISTING_ORDER=]
            [default: 6]
    -f, --fd <fd>
            Instead of binding to a TCP port, accept incoming connections to an already-bound TCP socket listener on the
            specified file descriptor number (usually zero). Requires that the parent process (e.g. inetd, launchd, or
@@ -65,7 +70,7 @@ OPTIONS:
        --page50x <page50x>
            HTML file path for 50x errors. If path is not specified or simply don't exists then server will use a
            generic HTML error message [env: SERVER_ERROR_PAGE_50X=]  [default: ./public/50x.html]
    -p, --port <port>                                      Host port [env: SERVER_PORT=]  [default: 80]
    -p, --port <port>                                          Host port [env: SERVER_PORT=]  [default: 80]
    -d, --root <root>
            Root directory path of static files [env: SERVER_ROOT=]  [default: ./public]

diff --git a/docs/content/configuration/environment-variables.md b/docs/content/configuration/environment-variables.md
index 5e1b62d..dad3f50 100644
--- a/docs/content/configuration/environment-variables.md
+++ b/docs/content/configuration/environment-variables.md
@@ -48,6 +48,9 @@ Specify a optional CORS list of allowed origin hosts separated by comas. Host po
### SERVER_DIRECTORY_LISTING
Enable directory listing for all requests ending with the slash character (‘/’). Default `false` (disabled).

### SERVER_DIRECTORY_LISTING_ORDER
Specify a default code number to order directory listing entries per `Name`, `Last modified` or `Size` attributes (columns). Code numbers supported: `0` (Name asc), `1` (Name desc), `2` (Last modified asc), `3` (Last modified desc), `4` (Size asc), `5` (Size desc). Default `6` (unordered).

### SERVER_SECURITY_HEADERS
Enable security headers by default when HTTP/2 feature is activated. Headers included: `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` (2 years max-age), `X-Frame-Options: DENY`, `X-XSS-Protection: 1; mode=block` and `Content-Security-Policy: frame-ancestors 'self'`. Default `false` (disabled).

diff --git a/docs/content/examples/directory-listing.md b/docs/content/examples/directory-listing.md
index 40ae875..86d50de 100644
--- a/docs/content/examples/directory-listing.md
+++ b/docs/content/examples/directory-listing.md
@@ -13,4 +13,44 @@ static-web-server \

And here an example of how the directory listing looks like.

<img title="SWS - Directory Listing" src="https://user-images.githubusercontent.com/1700322/118666481-81f22c80-b7f3-11eb-8c10-d530304e0e34.png" width="400">
<img title="SWS - Directory Listing" src="https://user-images.githubusercontent.com/1700322/145420578-5a508d2a-773b-4239-acc0-197ea2062ff4.png" width="400">

## Sorting

Ascending and descending ordering of files/dirs by their attributes are provided by the numeric `--directory-listing-order` option or the equivalent [SERVER_DIRECTORY_LISTING_ORDER]./../configuration/environment-variables.md#server_directory_listing_order env.

The possible number code values are grouped by attribute as follows:

### Name

- 0: Ascending
- 1: Descending

### Last modified

- 2: Ascending
- 3: Descending

### Size

- 4: Ascending
- 5: Descending

### Default

- 6: Unordered

!!! info "Tip"
    - The `--directory-listing-order` option depends on `--directory-listing` to be enabled.
    - Use the query `?sort=NUMBER` to customize the sorting. E.g `https://localhost/?sort=5` (sort by Size in descending order)

Example:

```sh
static-web-server \
    --port 8787 \
    --root ./my-public-dir \
    --directory-listing true \
    # Sorting file/dir names in descending order
    --directory-listing-order 1
```
diff --git a/src/config.rs b/src/config.rs
index f29acbe..7d29740 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -121,6 +121,15 @@ pub struct Config {

    #[structopt(
        long,
        required_if("directory_listing", "true"),
        default_value = "6",
        env = "SERVER_DIRECTORY_LISTING_ORDER"
    )]
    /// Specify a default code number to order directory listing entries per `Name`, `Last modified` or `Size` attributes (columns). Code numbers supported: 0 (Name asc), 1 (Name desc), 2 (Last modified asc), 3 (Last modified desc), 4 (Size asc), 5 (Size desc). Default 6 (unordered)
    pub directory_listing_order: u8,

    #[structopt(
        long,
        parse(try_from_str),
        required_if("http2", "true"),
        default_value_if("http2", Some("true"), "true"),
diff --git a/src/handler.rs b/src/handler.rs
index db67075..f1cb3d0 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -11,6 +11,7 @@ pub struct RequestHandlerOpts {
    pub root_dir: Arc<PathBuf>,
    pub compression: bool,
    pub dir_listing: bool,
    pub dir_listing_order: u8,
    pub cors: Option<Arc<cors::Configured>>,
    pub security_headers: bool,
    pub cache_control_headers: bool,
@@ -32,10 +33,13 @@ impl RequestHandler {
    ) -> impl Future<Output = Result<Response<Body>, Error>> + Send + 'a {
        let method = req.method();
        let headers = req.headers();
        let uri = req.uri();

        let root_dir = self.opts.root_dir.as_ref();
        let uri_path = req.uri().path();
        let uri_path = uri.path();
        let uri_query = uri.query();
        let dir_listing = self.opts.dir_listing;
        let dir_listing_order = self.opts.dir_listing_order;

        async move {
            // CORS
@@ -88,7 +92,17 @@ impl RequestHandler {
            }

            // Static files
            match static_files::handle(method, headers, root_dir, uri_path, dir_listing).await {
            match static_files::handle(
                method,
                headers,
                root_dir,
                uri_path,
                uri_query,
                dir_listing,
                dir_listing_order,
            )
            .await
            {
                Ok(mut resp) => {
                    // Auto compression based on the `Accept-Encoding` header
                    if self.opts.compression {
diff --git a/src/server.rs b/src/server.rs
index 191b3eb..8769033 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -102,6 +102,10 @@ impl Server {
        let dir_listing = opts.directory_listing;
        tracing::info!("directory listing: enabled={}", dir_listing);

        // Directory listing order number
        let dir_listing_order = opts.directory_listing_order;
        tracing::info!("directory listing order code: {}", dir_listing_order);

        // Cache control headers option
        let cache_control_headers = opts.cache_control_headers;
        tracing::info!("cache control headers: enabled={}", cache_control_headers);
@@ -123,6 +127,7 @@ impl Server {
                root_dir,
                compression,
                dir_listing,
                dir_listing_order,
                cors,
                security_headers,
                cache_control_headers,
diff --git a/src/static_files.rs b/src/static_files.rs
index 17509d5..dbae39c 100644
--- a/src/static_files.rs
+++ b/src/static_files.rs
@@ -12,6 +12,7 @@ use http::header::CONTENT_TYPE;
use humansize::{file_size_opts, FileSize};
use hyper::{Body, Method, Response, StatusCode};
use percent_encoding::percent_decode_str;
use std::cmp::Ordering;
use std::fs::Metadata;
use std::future::Future;
use std::io;
@@ -45,7 +46,9 @@ pub async fn handle(
    headers: &HeaderMap<HeaderValue>,
    base_path: impl Into<PathBuf>,
    uri_path: &str,
    uri_query: Option<&str>,
    dir_listing: bool,
    dir_listing_order: u8,
) -> Result<Response<Body>, StatusCode> {
    // Reject requests for non HEAD or GET methods
    if !(method == Method::HEAD || method == Method::GET) {
@@ -79,7 +82,14 @@ pub async fn handle(
            return Ok(resp);
        }

        return directory_listing(method, uri_path, filepath.as_ref()).await;
        return directory_listing(
            method,
            uri_path,
            uri_query,
            filepath.as_ref(),
            dir_listing_order,
        )
        .await;
    }

    file_reply(headers, (filepath, &meta, auto_index)).await
@@ -117,7 +127,9 @@ fn path_from_tail(
fn directory_listing<'a>(
    method: &'a Method,
    current_path: &'a str,
    uri_query: Option<&'a str>,
    filepath: &'a Path,
    dir_listing_order: u8,
) -> impl Future<Output = Result<Response<Body>, StatusCode>> + Send + 'a {
    let is_head = method == Method::HEAD;

@@ -130,7 +142,15 @@ fn directory_listing<'a>(

    tokio::fs::read_dir(parent).then(move |res| match res {
        Ok(entries) => Either::Left(async move {
            match read_directory_entries(entries, current_path, is_head).await {
            match read_directory_entries(
                entries,
                current_path,
                uri_query,
                is_head,
                dir_listing_order,
            )
            .await
            {
                Ok(resp) => Ok(resp),
                Err(err) => {
                    tracing::error!(
@@ -170,15 +190,14 @@ fn directory_listing<'a>(
async fn read_directory_entries(
    mut entries: tokio::fs::ReadDir,
    base_path: &str,
    uri_query: Option<&str>,
    is_head: bool,
    mut dir_listing_order: u8,
) -> Result<Response<Body>> {
    let mut entries_str = String::new();
    if base_path != "/" {
        entries_str = String::from(r#"<tr><td colspan="3"><a href="../">../</a></td></tr>"#);
    }

    let mut dirs_count: usize = 0;
    let mut files_count: usize = 0;
    let mut files_found: Vec<(String, String, u64, String)> = Vec::new();

    while let Some(entry) = entries.next_entry().await? {
        let meta = entry.metadata().await?;

@@ -187,16 +206,13 @@ async fn read_directory_entries(
            .into_string()
            .map_err(|err| anyhow::anyhow!(err.into_string().unwrap_or_default()))?;

        let mut filesize_str = String::from("-");
        let mut filesize = 0_u64;

        if meta.is_dir() {
            name += "/";
            dirs_count += 1;
        } else if meta.is_file() {
            filesize_str = meta
                .len()
                .file_size(file_size_opts::DECIMAL)
                .map_err(anyhow::Error::msg)?;
            filesize = meta.len();
            files_count += 1;
        } else if meta.file_type().is_symlink() {
            let m = tokio::fs::symlink_metadata(entry.path().canonicalize()?).await?;
@@ -204,10 +220,7 @@ async fn read_directory_entries(
                name += "/";
                dirs_count += 1;
            } else {
                filesize_str = meta
                    .len()
                    .file_size(file_size_opts::DECIMAL)
                    .map_err(anyhow::Error::msg)?;
                filesize = meta.len();
                files_count += 1;
            }
        } else {
@@ -222,9 +235,90 @@ async fn read_directory_entries(
                String::from("-")
            }
        };
        files_found.push((name, modified, filesize, uri));
    }

    // Check the query uri for a sorting type. E.g https://blah/?sort=5
    if let Some(q) = uri_query {
        let mut parts = form_urlencoded::parse(q.as_bytes());
        if parts.count() > 0 {
            // NOTE: we just pick up the first value (pairs)
            if let Some(sort) = parts.next() {
                if sort.0 == "sort" && !sort.1.trim().is_empty() {
                    match sort.1.parse::<u8>() {
                        Ok(n) => dir_listing_order = n,
                        Err(e) => {
                            tracing::debug!("sorting: query value to u8 error: {:?}", e);
                        }
                    }
                }
            }
        }
    }

    // Default sorting type values
    let mut sort_name = "0".to_owned();
    let mut sort_last_modified = "2".to_owned();
    let mut sort_size = "4".to_owned();

    files_found.sort_by(|a, b| match dir_listing_order {
        // Name (asc, desc)
        0 => {
            sort_name = "1".to_owned();
            a.0.to_lowercase().cmp(&b.0.to_lowercase())
        }
        1 => {
            sort_name = "0".to_owned();
            b.0.to_lowercase().cmp(&a.0.to_lowercase())
        }

        // Modified (asc, desc)
        2 => {
            sort_last_modified = "3".to_owned();
            a.1.cmp(&b.1)
        }
        3 => {
            sort_last_modified = "2".to_owned();
            b.1.cmp(&a.1)
        }

        // File size (asc, desc)
        4 => {
            sort_size = "5".to_owned();
            a.2.cmp(&b.2)
        }
        5 => {
            sort_size = "4".to_owned();
            b.2.cmp(&a.2)
        }

        // Unordered
        _ => Ordering::Equal,
    });

    // Prepare table header with sorting support
    let table_header = format!(
        r#"<thead><tr><th><a href="?sort={}">Name</a></th><th style="width:160px;"><a href="?sort={}">Last modified</a></th><th style="width:120px;text-align:right;"><a href="?sort={}">Size</a></th></tr></thead>"#,
        sort_name, sort_last_modified, sort_size,
    );

    let mut entries_str = String::new();
    if base_path != "/" {
        entries_str = String::from(r#"<tr><td colspan="3"><a href="../">../</a></td></tr>"#);
    }

    for f in files_found {
        let (name, modified, filesize, uri) = f;
        let mut filesize_str = filesize
            .file_size(file_size_opts::DECIMAL)
            .map_err(anyhow::Error::msg)?;

        if filesize == 0 {
            filesize_str = String::from("-");
        }

        entries_str = format!(
            "{}<tr><td><a href=\"{}\" title=\"{}\">{}</a></td><td style=\"width: 160px;\">{}</td><td align=\"right\">{}</td></tr>",
            "{}<tr><td><a href=\"{}\" title=\"{}\">{}</a></td><td>{}</td><td align=\"right\">{}</td></tr>",
            entries_str,
            uri,
            name,
@@ -244,10 +338,10 @@ async fn read_directory_entries(
        "<div>{} {}, {} {}</div>",
        dirs_count, dirs_str, files_count, "file(s)"
    );
    let style_str = r#"<style>html{background-color:#fff;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:20rem;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}body{padding:1rem;font-family:Consolas,'Liberation Mono',Menlo,monospace;font-size:.875rem;max-width:70rem;margin:0 auto;color:#4a4a4a;font-weight:400;line-height:1.5}h1{margin:0;padding:0;font-size:1.375rem;line-height:1.25;margin-bottom:0.5rem;}table{width:100%}table td{padding:.2rem .5rem;white-space:nowrap;vertical-align:top}table td a{display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:95%;vertical-align:top}table tr:hover td{background-color:#f5f5f5}footer{padding-top:0.5rem}</style>"#;
    let style_str = r#"<style>html{background-color:#fff;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:20rem;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}body{padding:1rem;font-family:Consolas,'Liberation Mono',Menlo,monospace;font-size:.875rem;max-width:70rem;margin:0 auto;color:#4a4a4a;font-weight:400;line-height:1.5}h1{margin:0;padding:0;font-size:1.375rem;line-height:1.25;margin-bottom:0.5rem;}table{width:100%;border-spacing: 0;}table th,table td{padding:.2rem .5rem;white-space:nowrap;vertical-align:top}table th a,table td a{display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:95%;vertical-align:top}table tr:hover td{background-color:#f5f5f5}footer{padding-top:0.5rem}table tr th{text-align:left;}</style>"#;
    let footer_str = r#"<footer>Powered by <a target="_blank" href="https://git.io/static-web-server">static-web-server</a> | MIT &amp; Apache 2.0</footer>"#;
    let page_str = format!(
        "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Index of {}</title>{}</head><body><h1>Index of {}</h1>{}<table><tr><th colspan=\"3\"><hr></th></tr>{}<tr><th colspan=\"3\"><hr></th></tr></table>{}</body></html>", current_path, style_str, current_path, summary_str, entries_str, footer_str
        "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><title>Index of {}</title>{}</head><body><h1>Index of {}</h1>{}<hr><table>{}{}</table><hr>{}</body></html>", current_path, style_str, current_path, summary_str, table_header, entries_str, footer_str
    );

    let mut resp = Response::new(Body::empty());
diff --git a/tests/dir_listing.rs b/tests/dir_listing.rs
index e5eb810..d363aae 100644
--- a/tests/dir_listing.rs
+++ b/tests/dir_listing.rs
@@ -27,8 +27,16 @@ mod tests {
            Method::TRACE,
        ];
        for method in methods {
            match static_files::handle(&method, &HeaderMap::new(), root_dir(), "/assets", true)
                .await
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "/assets",
                None,
                true,
                6,
            )
            .await
            {
                Ok(res) => {
                    assert_eq!(res.status(), 308);
diff --git a/tests/static_files.rs b/tests/static_files.rs
index 1235c9b..9631cd1 100644
--- a/tests/static_files.rs
+++ b/tests/static_files.rs
@@ -22,7 +22,9 @@ mod tests {
            &HeaderMap::new(),
            root_dir(),
            "index.html",
            None,
            false,
            6,
        )
        .await
        .expect("unexpected error response on `handle` function");
@@ -58,7 +60,9 @@ mod tests {
            &HeaderMap::new(),
            root_dir(),
            "index.html",
            None,
            false,
            6,
        )
        .await
        .expect("unexpected error response on `handle` function");
@@ -90,8 +94,16 @@ mod tests {
    #[tokio::test]
    async fn handle_file_not_found() {
        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &HeaderMap::new(), root_dir(), "xyz.html", false)
                .await
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "xyz.html",
                None,
                false,
                6,
            )
            .await
            {
                Ok(_) => {
                    panic!("expected a status error 404 but not status 200")
@@ -111,7 +123,16 @@ mod tests {

        for method in [Method::HEAD, Method::GET] {
            for uri in ["", "/"] {
                match static_files::handle(&method, &HeaderMap::new(), root_dir(), uri, false).await
                match static_files::handle(
                    &method,
                    &HeaderMap::new(),
                    root_dir(),
                    uri,
                    None,
                    false,
                    6,
                )
                .await
                {
                    Ok(res) => {
                        assert_eq!(res.status(), 200);
@@ -137,7 +158,9 @@ mod tests {
                &HeaderMap::new(),
                root_dir(),
                "/index%2ehtml",
                None,
                false,
                6,
            )
            .await
            {
@@ -160,7 +183,9 @@ mod tests {
                &HeaderMap::new(),
                root_dir(),
                "/%2E%2e.html",
                None,
                false,
                6,
            )
            .await
            {
@@ -186,7 +211,9 @@ mod tests {
                &HeaderMap::new(),
                root_dir(),
                "index.html",
                None,
                false,
                6,
            )
            .await
            {
@@ -207,7 +234,9 @@ mod tests {
                res1.headers()["last-modified"].to_owned(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 304);
                    assert_eq!(res.headers().get("content-length"), None);
@@ -228,7 +257,9 @@ mod tests {
                "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 200);
                    let body = hyper::body::to_bytes(res.body_mut())
@@ -252,7 +283,9 @@ mod tests {
                &HeaderMap::new(),
                root_dir(),
                "index.html",
                None,
                false,
                6,
            )
            .await
            {
@@ -272,7 +305,9 @@ mod tests {
                res1.headers()["last-modified"].to_owned(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(res) => {
                    assert_eq!(res.status(), 200);
                }
@@ -288,7 +323,9 @@ mod tests {
                "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 412);

@@ -319,8 +356,16 @@ mod tests {
            Method::TRACE,
        ];
        for method in methods {
            match static_files::handle(&method, &HeaderMap::new(), root_dir(), "index.html", false)
                .await
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "index.html",
                None,
                false,
                6,
            )
            .await
            {
                Ok(mut res) => match method {
                    // The handle only accepts HEAD or GET request methods
@@ -368,7 +413,9 @@ mod tests {
            let mut headers = HeaderMap::new();
            headers.insert(http::header::ACCEPT_ENCODING, enc.parse().unwrap());

            match static_files::handle(method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(res) => {
                    let res = compression::auto(method, &headers, res)
                        .expect("unexpected bytes error during body compression");
@@ -418,7 +465,9 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
@@ -448,7 +497,9 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 416);
                    assert_eq!(
@@ -479,7 +530,9 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(res) => {
                    assert_eq!(res.status(), 200);
                    assert_eq!(res.headers()["content-length"], buf.len().to_string());
@@ -502,7 +555,9 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
@@ -535,7 +590,9 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
@@ -565,7 +622,9 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 416);
                    assert_eq!(
@@ -598,7 +657,9 @@ mod tests {
        );

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 416);
                    assert_eq!(
@@ -629,7 +690,9 @@ mod tests {
        headers.insert("range", "bytes=".parse().unwrap());

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 200);
                    let body = hyper::body::to_bytes(res.body_mut())
@@ -655,7 +718,9 @@ mod tests {
        headers.insert("range", format!("bytes=100-{}", buf.len()).parse().unwrap());

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
@@ -692,7 +757,9 @@ mod tests {
        );

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", false).await {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(