index : static-web-server.git

ascending towards madness

#![forbid(unsafe_code)]
#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(dead_code)]

#[cfg(test)]
mod tests {
    use bytes::Bytes;
    use headers::HeaderMap;
    use http::{Method, StatusCode};
    use std::fs;
    use std::path::PathBuf;

    #[cfg(feature = "compression")]
    use static_web_server::compression;

    #[cfg(feature = "directory-listing")]
    use static_web_server::directory_listing::DirListFmt;
    use static_web_server::static_files::{self, HandleOpts};

    fn root_dir() -> PathBuf {
        PathBuf::from("docker/public/")
    }

    #[tokio::test]
    async fn handle_file() {
        let (mut res, _) = static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "index.html",
            uri_query: None,
            #[cfg(feature = "directory-listing")]
            dir_listing: false,
            #[cfg(feature = "directory-listing")]
            dir_listing_order: 6,
            #[cfg(feature = "directory-listing")]
            dir_listing_format: &DirListFmt::Html,
            redirect_trailing_slash: true,
            compression_static: false,
            ignore_hidden_files: false,
            index_files: &[],
        })
        .await
        .expect("unexpected error response on `handle` function");

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        assert_eq!(res.status(), 200);
        assert_eq!(res.headers()["content-length"], buf.len().to_string());
        assert_eq!(res.headers()["accept-ranges"], "bytes");
        assert!(!res.headers()["last-modified"].is_empty());

        let ctype = &res.headers()["content-type"];

        assert!(ctype == "text/html", "content-type is not html: {ctype:?}",);

        let body = hyper::body::to_bytes(res.body_mut())
            .await
            .expect("unexpected bytes error during `body` conversion");

        assert_eq!(body, buf);
    }

    #[tokio::test]
    async fn handle_file_head() {
        let (mut res, _) = static_files::handle(&HandleOpts {
            method: &Method::HEAD,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "index.html",
            uri_query: None,
            #[cfg(feature = "directory-listing")]
            dir_listing: false,
            #[cfg(feature = "directory-listing")]
            dir_listing_order: 6,
            #[cfg(feature = "directory-listing")]
            dir_listing_format: &DirListFmt::Html,
            redirect_trailing_slash: true,
            compression_static: false,
            ignore_hidden_files: false,
            index_files: &[],
        })
        .await
        .expect("unexpected error response on `handle` function");

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        assert_eq!(res.status(), 200);
        assert_eq!(res.headers()["content-length"], buf.len().to_string());
        assert_eq!(res.headers()["accept-ranges"], "bytes");
        assert!(!res.headers()["last-modified"].is_empty());

        let ctype = &res.headers()["content-type"];

        assert!(ctype == "text/html", "content-type is not html: {ctype:?}",);

        let body = hyper::body::to_bytes(res.body_mut())
            .await
            .expect("unexpected bytes error during `body` conversion");

        assert_eq!(body, buf);
    }

    #[tokio::test]
    async fn handle_file_not_found() {
        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "xyz.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok(_) => {
                    panic!("expected a status error 404 but not status 200")
                }
                Err(status) => {
                    assert_eq!(status, StatusCode::NOT_FOUND);
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_trailing_slash_redirection() {
        let (mut res, _) = static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "assets",
            uri_query: None,
            #[cfg(feature = "directory-listing")]
            dir_listing: false,
            #[cfg(feature = "directory-listing")]
            dir_listing_order: 0,
            #[cfg(feature = "directory-listing")]
            dir_listing_format: &DirListFmt::Html,
            redirect_trailing_slash: true,
            compression_static: false,
            ignore_hidden_files: false,
            index_files: &[],
        })
        .await
        .expect("unexpected error response on `handle` function");

        assert_eq!(res.status(), 308);
        assert_eq!(res.headers()["location"], "assets/");

        let body = hyper::body::to_bytes(res.body_mut())
            .await
            .expect("unexpected bytes error during `body` conversion");

        assert_eq!(body, Bytes::new());
    }

    #[tokio::test]
    async fn handle_trailing_slash_redirection_subdir() {
        match static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "assets",
            uri_query: None,
            #[cfg(feature = "directory-listing")]
            dir_listing: false,
            #[cfg(feature = "directory-listing")]
            dir_listing_order: 0,
            #[cfg(feature = "directory-listing")]
            dir_listing_format: &DirListFmt::Html,
            redirect_trailing_slash: true,
            compression_static: false,
            ignore_hidden_files: false,
            index_files: &[],
        })
        .await
        {
            Ok((res, _)) => {
                assert_eq!(res.status(), 308);
                assert_eq!(res.headers()["location"], "assets/");
            }
            Err(status) => {
                panic!("expected a status 308 but not a status {status}")
            }
        }
    }

    #[tokio::test]
    async fn handle_disabled_trailing_slash_redirection_subdir() {
        match static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "assets",
            uri_query: None,
            #[cfg(feature = "directory-listing")]
            dir_listing: false,
            #[cfg(feature = "directory-listing")]
            dir_listing_order: 0,
            #[cfg(feature = "directory-listing")]
            dir_listing_format: &DirListFmt::Html,
            redirect_trailing_slash: false,
            compression_static: false,
            ignore_hidden_files: false,
            index_files: &[],
        })
        .await
        {
            Ok((res, _)) => {
                assert_eq!(res.status(), 200);
            }
            Err(status) => {
                panic!("expected a status 200 but not a status {status}")
            }
        }
    }

    #[tokio::test]
    async fn handle_append_index_on_dir() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            for uri in ["", "/"] {
                match static_files::handle(&HandleOpts {
                    method: &method,
                    headers: &HeaderMap::new(),
                    base_path: &root_dir(),
                    uri_path: uri,
                    uri_query: None,
                    #[cfg(feature = "directory-listing")]
                    dir_listing: false,
                    #[cfg(feature = "directory-listing")]
                    dir_listing_order: 6,
                    #[cfg(feature = "directory-listing")]
                    dir_listing_format: &DirListFmt::Html,
                    redirect_trailing_slash: true,
                    compression_static: false,
                    ignore_hidden_files: false,
                    index_files: &[],
                })
                .await
                {
                    Ok((mut res, _)) => {
                        if uri.is_empty() {
                            // it should redirect permanently
                            assert_eq!(res.status(), 308);
                            assert_eq!(res.headers()["location"], "/");

                            let body = hyper::body::to_bytes(res.body_mut())
                                .await
                                .expect("unexpected bytes error during `body` conversion");

                            assert_eq!(body, Bytes::new());
                        } else {
                            // otherwise it should response with ok
                            assert_eq!(res.status(), 200);
                            assert_eq!(res.headers()["content-length"], buf.len().to_string());
                        }
                    }
                    Err(_) => {
                        panic!("expected a status 200 but not a status error")
                    }
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_file_encoded() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "/index%2ehtml",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((res, _)) => {
                    assert_eq!(res.status(), 200);
                    assert_eq!(res.headers()["content-length"], buf.len().to_string());
                }
                Err(_) => {
                    panic!("expected a status 200 but not a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_bad_encoded_path() {
        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "/%2E%2e.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok(_) => {
                    panic!("expected a status 200 but not a status error")
                }
                Err(status) => {
                    assert_eq!(status, 404);
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_not_modified() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            let res1 = match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((res, _)) => {
                    assert_eq!(res.status(), 200);
                    assert_eq!(res.headers()["content-length"], buf.len().to_string());
                    res
                }
                Err(_) => {
                    panic!("expected a status 200 but not a status error")
                }
            };

            // if-modified-since
            let mut headers = HeaderMap::new();
            headers.insert(
                "if-modified-since",
                res1.headers()["last-modified"].to_owned(),
            );

            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 304);
                    assert_eq!(res.headers().get("content-length"), None);
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, "");
                }
                Err(_) => {
                    panic!("expected a status 304 but not a status error")
                }
            }

            // clearly too old
            let mut headers = HeaderMap::new();
            headers.insert(
                "if-modified-since",
                "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap(),
            );

            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 200);
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, buf);
                    assert_eq!(res1.headers()["content-length"], buf.len().to_string());
                }
                Err(_) => {
                    panic!("expected a status 200 but not a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_precondition() {
        for method in [Method::HEAD, Method::GET] {
            let res1 = match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((res, _)) => {
                    assert_eq!(res.status(), 200);
                    res
                }
                Err(_) => {
                    panic!("expected a status 200 but not a status error")
                }
            };

            // if-unmodified-since
            let mut headers = HeaderMap::new();
            headers.insert(
                "if-unmodified-since",
                res1.headers()["last-modified"].to_owned(),
            );

            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((res, _)) => {
                    assert_eq!(res.status(), 200);
                }
                Err(_) => {
                    panic!("expected a status 200 but not a status error")
                }
            }

            // clearly too old
            let mut headers = HeaderMap::new();
            headers.insert(
                "if-unmodified-since",
                "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap(),
            );

            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 412);

                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");

                    assert_eq!(body, "");
                }
                Err(_) => {
                    panic!("expected a status 200 but not a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_file_allowed_disallowed_methods() {
        let methods = [
            Method::CONNECT,
            Method::DELETE,
            Method::GET,
            Method::HEAD,
            Method::PATCH,
            Method::POST,
            Method::PUT,
            Method::TRACE,
        ];
        for method in methods {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => match method {
                    // The handle only accepts HEAD or GET request methods
                    Method::GET | Method::HEAD => {
                        let buf = fs::read(root_dir().join("index.html"))
                            .expect("unexpected error during index.html reading");
                        let buf = Bytes::from(buf);

                        assert_eq!(res.status(), 200);
                        assert_eq!(res.headers()["content-length"], buf.len().to_string());
                        assert_eq!(res.headers()["accept-ranges"], "bytes");
                        assert!(!res.headers()["last-modified"].is_empty());

                        let ctype = &res.headers()["content-type"];

                        assert!(ctype == "text/html", "content-type is not html: {ctype:?}",);

                        let body = hyper::body::to_bytes(res.body_mut())
                            .await
                            .expect("unexpected bytes error during `body` conversion");

                        assert_eq!(body, buf);
                    }
                    _ => {
                        panic!("unexpected response for method {}", method.as_str())
                    }
                },
                Err(status) => {
                    assert_eq!(status, StatusCode::METHOD_NOT_ALLOWED);
                }
            }
        }
    }

    #[cfg(feature = "compression")]
    #[tokio::test]
    async fn handle_file_compressions() {
        let encodings = ["gzip", "deflate", "br", "zstd", "xyz"];
        let method = &Method::GET;

        for enc in encodings {
            let mut headers = HeaderMap::new();
            headers.insert(http::header::ACCEPT_ENCODING, enc.parse().unwrap());

            match static_files::handle(&HandleOpts {
                method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((res, _)) => {
                    let res = compression::auto(method, &headers, res)
                        .expect("unexpected bytes error during body compression");

                    let buf = fs::read(root_dir().join("index.html"))
                        .expect("unexpected error during index.html reading");

                    assert_eq!(res.status(), 200);
                    assert_eq!(res.headers()["accept-ranges"], "bytes");
                    assert!(!res.headers()["last-modified"].is_empty());

                    match enc {
                        // The handle only accepts `HEAD` or `GET` request methods
                        "gzip" | "deflate" | "br" | "zstd" => {
                            assert!(res.headers().get("content-length").is_none());
                            assert_eq!(res.headers()["content-encoding"], enc);
                        }
                        _ => {
                            // otherwise the compression doesn't happen because unsupported `accept-encoding`
                            assert_eq!(res.headers()["content-length"], buf.len().to_string());
                            assert!(res.headers().get("content-encoding").is_none());
                        }
                    };

                    let ctype = &res.headers()["content-type"];

                    assert!(ctype == "text/html", "content-type is not html: {ctype:?}",);
                }
                Err(_) => {
                    panic!("unexpected status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_single() {
        let mut headers = HeaderMap::new();
        headers.insert("range", "bytes=0-0".parse().unwrap());

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes 0-0/{}", buf.len())
                    );
                    assert_eq!(res.headers()["content-length"], "1");
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, &buf[..=0]);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_multiple() {
        let mut headers = HeaderMap::new();
        headers.insert("range", "bytes=100-200".parse().unwrap());

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes 100-200/{}", buf.len())
                    );
                    assert_eq!(res.headers()["content-length"], "101");
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, &buf[100..=200]);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_out_of_range() {
        let mut headers = HeaderMap::new();
        headers.insert("range", "bytes=100-100000".parse().unwrap());

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 416);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes */{}", buf.len())
                    );
                    assert_eq!(res.headers().get("content-length"), None);
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, "");
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_if_range_too_old() {
        let mut headers = HeaderMap::new();
        headers.insert("range", "bytes=100-200".parse().unwrap());
        headers.insert("if-range", "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap());

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((res, _)) => {
                    assert_eq!(res.status(), 200);
                    assert_eq!(res.headers()["content-length"], buf.len().to_string());
                    assert_eq!(res.headers().get("content-range"), None);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_suffix() {
        let mut headers = HeaderMap::new();
        headers.insert("range", "bytes=100-".parse().unwrap());

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes 100-{}/{}", buf.len() - 1, buf.len())
                    );
                    assert_eq!(
                        res.headers()["content-length"],
                        &buf[100..].len().to_string()
                    );
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, &buf[100..]);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_suffix_2() {
        let mut headers = HeaderMap::new();
        headers.insert("range", "bytes=-100".parse().unwrap());

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes {}-{}/{}", buf.len() - 100, buf.len() - 1, buf.len())
                    );
                    assert_eq!(res.headers()["content-length"], "100");
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, &buf[buf.len() - 100..]);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_bad() {
        let mut headers = HeaderMap::new();
        headers.insert("range", "bytes=100-10".parse().unwrap());

        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 416);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes */{}", buf.len())
                    );
                    assert_eq!(res.headers().get("content-length"), None);
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, "");
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_bad_2() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        let mut headers = HeaderMap::new();
        headers.insert(
            "range",
            format!("bytes=-{}", buf.len() + 1).parse().unwrap(),
        );

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 416);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes */{}", buf.len())
                    );
                    assert_eq!(res.headers().get("content-length"), None);
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, "");
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_bad_3() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        let mut headers = HeaderMap::new();
        // Range::Unbounded for beginning and end
        headers.insert("range", "bytes=".parse().unwrap());

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 200);
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, buf);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_exclude_file_size() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        let mut headers = HeaderMap::new();
        // range including end of file (non-inclusive result)
        headers.insert("range", format!("bytes=100-{}", buf.len()).parse().unwrap());

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes 100-{}/{}", buf.len() - 1, buf.len())
                    );
                    assert_eq!(
                        res.headers()["content-length"],
                        format!("{}", buf.len() - 100)
                    );
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, &buf[100..=buf.len() - 1]);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_byte_ranges_exclude_file_size_2() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
        let buf = Bytes::from(buf);

        let mut headers = HeaderMap::new();
        // range with 1 byte to end yields same result as above. (inclusive result)
        headers.insert(
            "range",
            format!("bytes=100-{}", buf.len() - 1).parse().unwrap(),
        );

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: false,
                ignore_hidden_files: false,
                index_files: &[],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 206);
                    assert_eq!(
                        res.headers()["content-range"],
                        format!("bytes 100-{}/{}", buf.len() - 1, buf.len())
                    );
                    assert_eq!(
                        res.headers()["content-length"],
                        format!("{}", buf.len() - 100)
                    );
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, &buf[100..=buf.len() - 1]);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_ignore_hidden_files() {
        let root_dir = PathBuf::from("tests/fixtures/public/");
        let headers = HeaderMap::new();

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir,
                uri_path: ".dotfile",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: true,
                ignore_hidden_files: true,
                index_files: &[],
            })
            .await
            {
                Ok(_) => {
                    panic!("expected a status error 404 but not status 200")
                }
                Err(status) => {
                    assert_eq!(status, StatusCode::NOT_FOUND);
                }
            }
        }
    }

    #[tokio::test]
    async fn handle_multiple_index_files() {
        let root_dir = PathBuf::from("tests/fixtures/public/");
        let headers = HeaderMap::new();

        let buf = fs::read(root_dir.join("index.htm"))
            .expect("unexpected error during index.htm reading");
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir,
                uri_path: "/",
                uri_query: None,
                #[cfg(feature = "directory-listing")]
                dir_listing: false,
                #[cfg(feature = "directory-listing")]
                dir_listing_order: 6,
                #[cfg(feature = "directory-listing")]
                dir_listing_format: &DirListFmt::Html,
                redirect_trailing_slash: true,
                compression_static: true,
                ignore_hidden_files: true,
                index_files: &["index.html", "index.htm"],
            })
            .await
            {
                Ok((mut res, _)) => {
                    assert_eq!(res.status(), 200);
                    assert_eq!(res.headers()["content-length"], format!("{}", buf.len()));
                    let body = hyper::body::to_bytes(res.body_mut())
                        .await
                        .expect("unexpected bytes error during `body` conversion");
                    assert_eq!(body, &buf);
                }
                Err(_) => {
                    panic!("expected a normal response rather than a status error")
                }
            }
        }
    }
}