From 688d1b271c2df2dfd4454ef4a8eecb2d5c44311d Mon Sep 17 00:00:00 2001 From: Jose Quintana <1700322+joseluisq@users.noreply.github.com> Date: Sat, 11 Dec 2021 00:24:50 +0100 Subject: [PATCH] Merge pull request #71 from joseluisq/feature/directory_listing_sorting feat: ascending & descending ordering support for directory listing --- 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 361d9d6..ba84cfa 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 A blazing fast and asynchronous web server for static files-serving. @@ -37,6 +37,11 @@ OPTIONS: -z, --directory-listing Enable directory listing for all requests ending with the slash character (‘/’) [env: SERVER_DIRECTORY_LISTING=] [default: false] + --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 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 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 Host port [env: SERVER_PORT=] [default: 80] + -p, --port Host port [env: SERVER_PORT=] [default: 80] -d, --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. - + + +## 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, pub compression: bool, pub dir_listing: bool, + pub dir_listing_order: u8, pub cors: Option>, pub security_headers: bool, pub cache_control_headers: bool, @@ -32,10 +33,13 @@ impl RequestHandler { ) -> impl Future, 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, base_path: impl Into, uri_path: &str, + uri_query: Option<&str>, dir_listing: bool, + dir_listing_order: u8, ) -> Result, 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, 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> { - let mut entries_str = String::new(); - if base_path != "/" { - entries_str = String::from(r#"../"#); - } - 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::() { + 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#"NameLast modifiedSize"#, + sort_name, sort_last_modified, sort_size, + ); + + let mut entries_str = String::new(); + if base_path != "/" { + entries_str = String::from(r#"../"#); + } + + 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!( - "{}{}{}{}", + "{}{}{}{}", entries_str, uri, name, @@ -244,10 +338,10 @@ async fn read_directory_entries( "
{} {}, {} {}
", dirs_count, dirs_str, files_count, "file(s)" ); - let style_str = r#""#; + let style_str = r#""#; let footer_str = r#""#; let page_str = format!( - "Index of {}{}

Index of {}

{}{}


{}", current_path, style_str, current_path, summary_str, entries_str, footer_str + "Index of {}{}

Index of {}

{}
{}{}

{}", 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!( -- libgit2 1.7.2