Repository: static-web-server/static-web-server (language: rust)

Issue:
Custom index file
you provide customisation through SERVER_ROOT env variable which is good, but what about SERVER_INDEX_FILE? if someone want to have different name than index.html for default file. please provide this ENV variable



Discussion / hints:
Probably something similar to what Nginx Index module does like `index index.html index.htm;` for all requests ending with the slash.

We could go with something like:

```sh
SERVER_INDEX_FILES="index.html index.htm etc" 
```
Yes 👍

Fix patch:
diff --git a/README.md b/README.md
index fb06318e..b3144b49 100644
--- a/README.md
+++ b/README.md
@@ -65,6 +65,7 @@ Cross-platform and available for `Linux`, `macOS`, `Windows`, `FreeBSD`, `NetBSD
 - Support for serving pre-compressed (Gzip/Brotli/Zstd) files directly from disk.
 - Custom URL rewrites and redirects via glob patterns with replacements.
 - Virtual hosting support.
+- Multiple index files.
 - Available as a library crate with opt-in features.
 - First-class [Docker](https://docs.docker.com/get-started/overview/) support. [Scratch](https://hub.docker.com/_/scratch), latest [Alpine Linux](https://hub.docker.com/_/alpine) and [Debian](https://hub.docker.com/_/alpine) Docker images.
 - Ability to accept a socket listener as a file descriptor for sandboxing and on-demand applications (e.g. [systemd](http://0pointer.de/blog/projects/socket-activation.html)).
diff --git a/docs/content/configuration/command-line-arguments.md b/docs/content/configuration/command-line-arguments.md
index cea36a42..3d740c66 100644
--- a/docs/content/configuration/command-line-arguments.md
+++ b/docs/content/configuration/command-line-arguments.md
@@ -55,6 +55,8 @@ Options:
           HTTP host port where the redirect server will listen for requests to redirect them to HTTPS. It depends on "https_redirect" to be enabled [env: SERVER_HTTPS_REDIRECT_FROM_PORT=] [default: 80]
       --https-redirect-from-hosts <HTTPS_REDIRECT_FROM_HOSTS>
           List of host names or IPs allowed to redirect from. HTTP requests must contain the HTTP 'Host' header and match against this list. It depends on "https_redirect" to be enabled [env: SERVER_HTTPS_REDIRECT_FROM_HOSTS=] [default: localhost]
+      --index-files <INDEX_FILES>
+          List of files that will be used as an index for requests ending with the slash character (‘/’). Files are checked in the specified order [env: SERVER_INDEX_FILES=] [default: index.html]
   -x, --compression[=<COMPRESSION>]
           Gzip, Deflate, Brotli or Zstd compression on demand determined by the Accept-Encoding header and applied to text-based web file types only [env: SERVER_COMPRESSION=] [default: true] [possible values: true, false]
       --compression-static[=<COMPRESSION_STATIC>]
diff --git a/docs/content/configuration/config-file.md b/docs/content/configuration/config-file.md
index 0e8362ea..b5c3a430 100644
--- a/docs/content/configuration/config-file.md
+++ b/docs/content/configuration/config-file.md
@@ -78,6 +78,9 @@ compression-static = true
 #### Health-check endpoint (GET or HEAD `/health`)
 health = false
 
+#### List of index files
+# index-files = "index.html, index.htm"
+
 ### Windows Only
 
 #### Run the web server as a Windows Service
diff --git a/docs/content/configuration/environment-variables.md b/docs/content/configuration/environment-variables.md
index 0eb3b47e..4a804e18 100644
--- a/docs/content/configuration/environment-variables.md
+++ b/docs/content/configuration/environment-variables.md
@@ -108,6 +108,9 @@ Ignore hidden files/directories (dotfiles), preventing them to be served and bei
 ### SERVER_HEALTH
 Activate the health endpoint.
 
+### SERVER_INDEX_FILES
+List of files that will be used as an index for requests ending with the slash character (‘/’). Files are checked in the specified order. Default `index.html`.
+
 ## Windows
 The following options and commands are Windows platform-specific.
 
diff --git a/docs/content/features/multiple-index-files.md b/docs/content/features/multiple-index-files.md
new file mode 100644
index 00000000..65cf85cc
--- /dev/null
+++ b/docs/content/features/multiple-index-files.md
@@ -0,0 +1,17 @@
+# Multiple index files
+
+**`SWS`** allows to provide a list of files that will be used as an index for requests ending with the slash character (‘/’).
+
+!!! info "Notes"
+    - Files are checked in the specified order from left to right.
+    - The option value can be a single index or comma-separated when multiple values.
+    - The default value is `index.html`.
+
+This feature is disabled by default and can be controlled by the string list `--index-files` option or the equivalent [SERVER_INDEX_FILES](./../configuration/environment-variables.md#server_index_files) env.
+
+Here is an example:
+
+```sh
+static-web-server -p 8787 -d ./public \
+    --index-files="index.html, index.htm, default.html"
+```
diff --git a/docs/content/index.md b/docs/content/index.md
index 7f6d4b5e..47e1d63e 100644
--- a/docs/content/index.md
+++ b/docs/content/index.md
@@ -69,6 +69,7 @@ Cross-platform and available for `Linux`, `macOS`, `Windows`, `FreeBSD`, `NetBSD
 - Support for serving pre-compressed (Gzip/Brotli/Zstd) files directly from disk.
 - Custom URL rewrites and redirects via glob patterns with replacements.
 - Virtual hosting support.
+- Multiple index files.
 - Available as a library crate with opt-in features.
 - First-class [Docker](https://docs.docker.com/get-started/overview/) support. [Scratch](https://hub.docker.com/_/scratch), latest [Alpine Linux](https://hub.docker.com/_/alpine) and [Debian](https://hub.docker.com/_/alpine) Docker images.
 - Ability to accept a socket listener as a file descriptor for sandboxing and on-demand applications (e.g. [systemd](http://0pointer.de/blog/projects/socket-activation.html)).
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
index 455f0bb5..cbc05a15 100644
--- a/docs/mkdocs.yml
+++ b/docs/mkdocs.yml
@@ -163,6 +163,7 @@ nav:
     - 'Ignore Files': 'features/ignore-files.md'
     - 'Health endpoint': 'features/health-endpoint.md'
     - 'Virtual Hosting': 'features/virtual-hosting.md'
+    - 'Multiple Index Files': 'features/multiple-index-files.md'
     - 'WebAssembly': 'features/webassembly.md'
   - 'Platforms & Architectures': 'platforms-architectures.md'
   - 'Migrating from v1 to v2': 'migration.md'
diff --git a/src/handler.rs b/src/handler.rs
index b07ed4ef..fb548c5e 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -70,6 +70,8 @@ pub struct RequestHandlerOpts {
     #[cfg(feature = "basic-auth")]
     #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
     pub basic_auth: String,
+    /// Index files feature.
+    pub index_files: Vec<String>,
     /// Log remote address feature.
     pub log_remote_address: bool,
     /// Redirect trailing slash feature.
@@ -114,6 +116,7 @@ impl RequestHandler {
         let compression_static = self.opts.compression_static;
         let ignore_hidden_files = self.opts.ignore_hidden_files;
         let health = self.opts.health;
+        let index_files: Vec<&str> = self.opts.index_files.iter().map(|s| s.as_str()).collect();
 
         let mut cors_headers: Option<http::HeaderMap> = None;
 
@@ -359,6 +362,7 @@ impl RequestHandler {
             }
 
             let uri_path = &uri_path;
+            let index_files = index_files.as_ref();
 
             // Static files
             match static_files::handle(&HandleOpts {
@@ -376,6 +380,7 @@ impl RequestHandler {
                 redirect_trailing_slash,
                 compression_static,
                 ignore_hidden_files,
+                index_files,
             })
             .await
             {
diff --git a/src/server.rs b/src/server.rs
index f4632f3e..4cae50f7 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -252,6 +252,17 @@ impl Server {
         let grace_period = general.grace_period;
         server_info!("grace period before graceful shutdown: {}s", grace_period);
 
+        // Index files option
+        let index_files = general
+            .index_files
+            .split(',')
+            .map(|s| s.trim().to_owned())
+            .collect::<Vec<_>>();
+        if index_files.is_empty() {
+            bail!("index files list is empty, provide at least one index file")
+        }
+        server_info!("index files: {}", general.index_files);
+
         // Health endpoint option
         let health = general.health;
         server_info!("health endpoint: enabled={}", health);
@@ -280,6 +291,7 @@ impl Server {
                 log_remote_address,
                 redirect_trailing_slash,
                 ignore_hidden_files,
+                index_files,
                 health,
                 advanced_opts,
             }),
@@ -419,6 +431,7 @@ impl Server {
                     bail!("https redirect allowed hosts is empty, provide at least one host or IP")
                 }
 
+                // Redirect options
                 let redirect_opts = Arc::new(https_redirect::RedirectOpts {
                     https_hostname: general.https_redirect_host,
                     https_port: general.port,
diff --git a/src/settings/cli.rs b/src/settings/cli.rs
index 470f58b5..c1320d35 100644
--- a/src/settings/cli.rs
+++ b/src/settings/cli.rs
@@ -226,6 +226,11 @@ pub struct General {
     /// List of host names or IPs allowed to redirect from. HTTP requests must contain the HTTP 'Host' header and match against this list. It depends on "https_redirect" to be enabled.
     pub https_redirect_from_hosts: String,
 
+    #[arg(long, default_value = "index.html", env = "SERVER_INDEX_FILES")]
+    /// List of files that will be used as an index for requests ending with the slash character (‘/’).
+    /// Files are checked in the specified order.
+    pub index_files: String,
+
     #[cfg(feature = "compression")]
     #[cfg_attr(docsrs, doc(cfg(feature = "compression")))]
     #[arg(
diff --git a/src/settings/file.rs b/src/settings/file.rs
index cb2dd4ed..69bc865e 100644
--- a/src/settings/file.rs
+++ b/src/settings/file.rs
@@ -187,6 +187,9 @@ pub struct General {
     /// Cors expose headers feature.
     pub cors_expose_headers: Option<String>,
 
+    /// List of files to be used as an index for requests ending with the slash character (‘/’).
+    pub index_files: Option<String>,
+
     /// Directory listing feature.
     #[cfg(feature = "directory-listing")]
     #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index 39023b2d..4de7f7d6 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -145,6 +145,7 @@ impl Settings {
         let mut log_remote_address = opts.log_remote_address;
         let mut redirect_trailing_slash = opts.redirect_trailing_slash;
         let mut ignore_hidden_files = opts.ignore_hidden_files;
+        let mut index_files = opts.index_files;
         let mut health = opts.health;
 
         // Windows-only options
@@ -284,6 +285,9 @@ impl Settings {
                 if let Some(v) = general.health {
                     health = v
                 }
+                if let Some(v) = general.index_files {
+                    index_files = v
+                }
 
                 // Windows-only options
                 #[cfg(windows)]
@@ -508,6 +512,7 @@ impl Settings {
                 log_remote_address,
                 redirect_trailing_slash,
                 ignore_hidden_files,
+                index_files,
                 health,
 
                 // Windows-only options and commands
diff --git a/src/static_files.rs b/src/static_files.rs
index 12ee3f4f..1875ee74 100644
--- a/src/static_files.rs
+++ b/src/static_files.rs
@@ -39,6 +39,8 @@ use crate::{
     directory_listing::{DirListFmt, DirListOpts},
 };
 
+const DEFAULT_INDEX_FILES: &[&str; 1] = &["index.html"];
+
 /// Defines all options needed by the static-files handler.
 pub struct HandleOpts<'a> {
     /// Request method.
@@ -49,6 +51,8 @@ pub struct HandleOpts<'a> {
     pub base_path: &'a PathBuf,
     /// Request base path.
     pub uri_path: &'a str,
+    /// Index files.
+    pub index_files: &'a [&'a str],
     /// Request URI query.
     pub uri_query: Option<&'a str>,
     /// Directory listing feature.
@@ -91,7 +95,13 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<(Response<Body>, bool),
         metadata,
         is_dir,
         precompressed_variant,
-    } = composed_file_metadata(&mut file_path, headers_opt, compression_static_opt).await?;
+    } = composed_file_metadata(
+        &mut file_path,
+        headers_opt,
+        compression_static_opt,
+        opts.index_files,
+    )
+    .await?;
 
     // Check for a hidden file/directory (dotfile) and ignore it if feature enabled
     if opts.ignore_hidden_files && file_path.is_hidden() {
@@ -215,48 +225,63 @@ async fn composed_file_metadata<'a>(
     mut file_path: &'a mut PathBuf,
     _headers: &'a HeaderMap<HeaderValue>,
     _compression_static: bool,
+    mut index_files: &'a [&'a str],
 ) -> Result<FileMetadata<'a>, StatusCode> {
     tracing::trace!("getting metadata for file {}", file_path.display());
 
     match file_metadata(file_path) {
         Ok((mut metadata, is_dir)) => {
             if is_dir {
-                // Append a HTML index page by default if it's a directory path (`autoindex`)
-                tracing::debug!("dir: appending an index.html to the directory path");
-                file_path.push("index.html");
-
-                // Pre-compressed variant check for the autoindex
-                #[cfg(feature = "compression")]
-                if _compression_static {
-                    if let Some(p) =
-                        compression_static::precompressed_variant(file_path, _headers).await
-                    {
-                        return Ok(FileMetadata {
-                            file_path,
-                            metadata: p.metadata,
-                            is_dir: false,
-                            precompressed_variant: Some((p.file_path, p.extension)),
-                        });
-                    }
+                // Try every index file variant in order
+                if index_files.is_empty() {
+                    index_files = DEFAULT_INDEX_FILES;
                 }
+                let mut index_found = false;
+                for index in index_files {
+                    // Append a HTML index page by default if it's a directory path (`autoindex`)
+                    tracing::debug!("dir: appending {} to the directory path", index);
+                    file_path.push(index);
+
+                    // Pre-compressed variant check for the autoindex
+                    #[cfg(feature = "compression")]
+                    if _compression_static {
+                        if let Some(p) =
+                            compression_static::precompressed_variant(file_path, _headers).await
+                        {
+                            return Ok(FileMetadata {
+                                file_path,
+                                metadata: p.metadata,
+                                is_dir: false,
+                                precompressed_variant: Some((p.file_path, p.extension)),
+                            });
+                        }
+                    }
 
-                // Otherwise, just fallback to finding the index.html
-                // and overwrite the current `meta`
-                // Also noting that it's still a directory request
-                if let Ok(meta_res) = file_metadata(file_path) {
-                    (metadata, _) = meta_res
-                } else {
-                    // We remove the appended index.html
-                    file_path.pop();
-                    let new_meta: Option<Metadata>;
-                    (file_path, new_meta) = suffix_file_html_metadata(file_path);
-                    if let Some(new_meta) = new_meta {
-                        metadata = new_meta;
+                    // Otherwise, just fallback to finding the index.html
+                    // and overwrite the current `meta`
+                    // Also noting that it's still a directory request
+                    if let Ok(meta_res) = file_metadata(file_path) {
+                        (metadata, _) = meta_res;
+                        index_found = true;
+                        break;
                     } else {
-                        // We append the index.html to preserve previous behavior
-                        file_path.push("index.html");
+                        // We remove only the appended index file
+                        file_path.pop();
+                        let new_meta: Option<Metadata>;
+                        (file_path, new_meta) = suffix_file_html_metadata(file_path);
+                        if let Some(new_meta) = new_meta {
+                            metadata = new_meta;
+                            index_found = true;
+                            break;
+                        }
                     }
                 }
+
+                // In case no index was found then we append the last index
+                // of the list to preserve the previous behavior
+                if !index_found && !index_files.is_empty() {
+                    file_path.push(index_files.last().unwrap());
+                }
             } else {
                 // Fallback pre-compressed variant check for the specific file
                 #[cfg(feature = "compression")]


Test patch:
diff --git a/tests/compression_static.rs b/tests/compression_static.rs
index 35f291b0..f614c6cb 100644
--- a/tests/compression_static.rs
+++ b/tests/compression_static.rs
@@ -48,6 +48,7 @@ mod tests {
             #[cfg(feature = "compression")]
             compression_static: true,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         .expect("unexpected error response on `handle` function");
@@ -106,6 +107,7 @@ mod tests {
             #[cfg(feature = "compression")]
             compression_static: true,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         .expect("unexpected error response on `handle` function");
@@ -158,6 +160,7 @@ mod tests {
             redirect_trailing_slash: true,
             compression_static: true,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         .expect("unexpected error response on `handle` function");
diff --git a/tests/dir_listing.rs b/tests/dir_listing.rs
index b1b2b27c..851b089b 100644
--- a/tests/dir_listing.rs
+++ b/tests/dir_listing.rs
@@ -49,6 +49,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -79,6 +80,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -119,6 +121,7 @@ mod tests {
                 redirect_trailing_slash: false,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -159,6 +162,7 @@ mod tests {
                 redirect_trailing_slash: false,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -189,6 +193,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -240,6 +245,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: true,
+                index_files: &[],
             })
             .await
             {
@@ -254,7 +260,7 @@ mod tests {
 
                     if method == Method::GET {
                         let entries: Vec<FileEntry> = serde_json::from_str(body_str).unwrap();
-                        assert_eq!(entries.len(), 2);
+                        assert_eq!(entries.len(), 3);
 
                         let first_entry = entries.first().unwrap();
                         assert_eq!(first_entry.name, "spécial directöry");
@@ -263,10 +269,10 @@ mod tests {
                         assert!(first_entry.size.is_none());
 
                         let last_entry = entries.last().unwrap();
-                        assert_eq!(last_entry.name, "index.html.gz");
+                        assert_eq!(last_entry.name, "index.htm");
                         assert_eq!(last_entry.typed, "file");
                         assert!(!last_entry.mtime.is_empty());
-                        assert!(last_entry.size.unwrap() > 300);
+                        assert!(last_entry.size.unwrap() >= 36);
                     } else {
                         assert!(body_str.is_empty());
                     }
@@ -309,6 +315,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -351,6 +358,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: true,
+                index_files: &[],
             })
             .await
             {
diff --git a/tests/fixtures/public/index.htm b/tests/fixtures/public/index.htm
new file mode 100644
index 00000000..ad3452ff
--- /dev/null
+++ b/tests/fixtures/public/index.htm
@@ -0,0 +1,1 @@
+<h1>this is a custom index file</h1>
diff --git a/tests/static_files.rs b/tests/static_files.rs
index 6007ab04..0c55a540 100644
--- a/tests/static_files.rs
+++ b/tests/static_files.rs
@@ -39,6 +39,7 @@ mod tests {
             redirect_trailing_slash: true,
             compression_static: false,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         .expect("unexpected error response on `handle` function");
@@ -80,6 +81,7 @@ mod tests {
             redirect_trailing_slash: true,
             compression_static: false,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         .expect("unexpected error response on `handle` function");
@@ -122,6 +124,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -152,6 +155,7 @@ mod tests {
             redirect_trailing_slash: true,
             compression_static: false,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         .expect("unexpected error response on `handle` function");
@@ -183,6 +187,7 @@ mod tests {
             redirect_trailing_slash: true,
             compression_static: false,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         {
@@ -213,6 +218,7 @@ mod tests {
             redirect_trailing_slash: false,
             compression_static: false,
             ignore_hidden_files: false,
+            index_files: &[],
         })
         .await
         {
@@ -248,6 +254,7 @@ mod tests {
                     redirect_trailing_slash: true,
                     compression_static: false,
                     ignore_hidden_files: false,
+                    index_files: &[],
                 })
                 .await
                 {
@@ -298,6 +305,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -330,6 +338,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -365,6 +374,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -400,6 +410,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -438,6 +449,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -474,6 +486,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -508,6 +521,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -541,6 +555,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -588,6 +603,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -649,6 +665,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -712,6 +729,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -759,6 +777,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -806,6 +825,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -854,6 +874,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -894,6 +915,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -944,6 +966,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -991,6 +1014,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -1041,6 +1065,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -1089,6 +1114,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -1132,6 +1158,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -1186,6 +1213,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: false,
                 ignore_hidden_files: false,
+                index_files: &[],
             })
             .await
             {
@@ -1232,6 +1260,7 @@ mod tests {
                 redirect_trailing_slash: true,
                 compression_static: true,
                 ignore_hidden_files: true,
+                index_files: &[],
             })
             .await
             {
@@ -1244,4 +1273,48 @@ mod tests {
             }
         }
     }
+
+    #[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")
+                }
+            }
+        }
+    }
 }
diff --git a/tests/toml/config.toml b/tests/toml/config.toml
index 3d3b3ffc..3bbf5a66 100644
--- a/tests/toml/config.toml
+++ b/tests/toml/config.toml
@@ -65,6 +65,9 @@ redirect-trailing-slash = true
 #### Check for existing pre-compressed files
 compression-static = false
 
+#### List of index files
+index-files = "index.html, index.htm"
+
 ### Windows Only
 
 #### Run the web server as a Windows Service