Skip to main content

static_web_server/settings/
mod.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// This file is part of Static Web Server.
3// See https://static-web-server.net/ for more information
4// Copyright (C) 2019-present Jose Quintana <joseluisq.net>
5
6//! Module that provides all settings of SWS.
7//!
8
9use aho_corasick::AhoCorasick;
10use clap::Parser;
11use globset::{Glob, GlobBuilder, GlobMatcher};
12use headers::HeaderMap;
13use hyper::StatusCode;
14use regex_lite::Regex;
15use std::path::{Path, PathBuf};
16
17use crate::{Context, Result, helpers, logger};
18
19pub mod cli;
20#[doc(hidden)]
21pub mod cli_output;
22pub mod file;
23
24pub use cli::Commands;
25
26use cli::General;
27
28use self::file::MemoryCache;
29
30use self::file::{RedirectsKind, Settings as FileSettings};
31
32#[cfg(any(
33    feature = "compression",
34    feature = "compression-gzip",
35    feature = "compression-brotli",
36    feature = "compression-zstd",
37    feature = "compression-deflate"
38))]
39pub use file::CompressionLevel;
40
41/// The `headers` file options.
42pub struct Headers {
43    /// Source pattern glob matcher
44    pub source: GlobMatcher,
45    /// Map of custom HTTP headers
46    pub headers: HeaderMap,
47}
48
49/// The `Rewrites` file options.
50pub struct Rewrites {
51    /// Source pattern Regex matcher
52    pub source: Regex,
53    /// A local file that must exist
54    pub destination: String,
55    /// Optional redirect type either 301 (Moved Permanently) or 302 (Found).
56    pub redirect: Option<RedirectsKind>,
57    /// Pre-compiled Aho-Corasick automaton for placeholder replacement.
58    pub replacer: AhoCorasick,
59}
60
61/// The `Redirects` file options.
62pub struct Redirects {
63    /// Optional host to match against an incoming URI host if specified
64    pub host: Option<String>,
65    /// Source pattern Regex matcher
66    pub source: Regex,
67    /// A local file that must exist
68    pub destination: String,
69    /// Redirection type either 301 (Moved Permanently) or 302 (Found)
70    pub kind: StatusCode,
71    /// Pre-compiled Aho-Corasick automaton for placeholder replacement.
72    pub replacer: AhoCorasick,
73}
74
75/// The `VirtualHosts` file options.
76pub struct VirtualHosts {
77    /// The value to check for in the "Host" header
78    pub host: String,
79    /// The root directory for this virtual host
80    pub root: PathBuf,
81}
82
83/// The `advanced` file options.
84#[derive(Default)]
85pub struct Advanced {
86    /// Headers list.
87    pub headers: Option<Vec<Headers>>,
88    /// Rewrites list.
89    pub rewrites: Option<Vec<Rewrites>>,
90    /// Redirects list.
91    pub redirects: Option<Vec<Redirects>>,
92    /// Name-based virtual hosting
93    pub virtual_hosts: Option<Vec<VirtualHosts>>,
94    /// In-memory cache configuration.
95    pub memory_cache: Option<MemoryCache>,
96}
97
98/// Build an `AhoCorasick` automaton for the placeholder patterns `$0`, `$1`, ..., `$N`
99/// based on the number of capture groups in the given regex.
100pub fn build_placeholder_replacer(regex: &Regex) -> AhoCorasick {
101    let patterns: Vec<String> = (0..regex.captures_len()).map(|i| format!("${i}")).collect();
102    AhoCorasick::new(&patterns).expect("failed to build Aho-Corasick automaton for placeholders")
103}
104
105/// The full server CLI and File options.
106pub struct Settings {
107    /// General server options
108    pub general: General,
109    /// Advanced server options
110    pub advanced: Option<Advanced>,
111}
112
113impl Settings {
114    /// Reads CLI/Env and config file options returning the server settings.
115    /// It also takes care to initialize the logging system with its level
116    /// once the `general` settings are determined.
117    pub fn get(log_init: bool) -> Result<Settings> {
118        Self::parse_from(log_init, None)
119    }
120
121    /// Reads CLI/Env and config file options returning the server settings
122    /// without parsing arguments useful for testing.
123    pub fn get_unparsed(log_init: bool, args: &[&str]) -> Result<Settings> {
124        Self::parse_from(log_init, Some(args))
125    }
126
127    fn parse_from(log_init: bool, args: Option<&[&str]>) -> Result<Settings> {
128        let opts = match args {
129            Some(v) => General::parse_from(v),
130            None => General::parse(),
131        };
132
133        // Define the general CLI/file options
134        let version = opts.version;
135        let mut host = opts.host;
136        let mut port = opts.port;
137        let mut root = opts.root;
138        let mut log_level = opts.log_level;
139        let mut log_with_ansi = opts.log_with_ansi;
140        let mut log_format = opts.log_format;
141        let mut log_file = opts.log_file.clone();
142        let mut config_file = opts.config_file.clone();
143        let mut cache_control_headers = opts.cache_control_headers;
144        let mut etag = opts.etag;
145
146        #[cfg(any(
147            feature = "compression",
148            feature = "compression-gzip",
149            feature = "compression-brotli",
150            feature = "compression-zstd",
151            feature = "compression-deflate"
152        ))]
153        let mut compression = opts.compression;
154        #[cfg(any(
155            feature = "compression",
156            feature = "compression-gzip",
157            feature = "compression-brotli",
158            feature = "compression-zstd",
159            feature = "compression-deflate"
160        ))]
161        let mut compression_level = opts.compression_level;
162
163        let mut compression_static = opts.compression_static;
164
165        let mut page404 = opts.page404;
166        let mut page50x = opts.page50x;
167
168        #[cfg(feature = "tls")]
169        let mut tls = opts.tls;
170        #[cfg(feature = "tls")]
171        let mut tls_cert = opts.tls_cert;
172        #[cfg(feature = "tls")]
173        let mut tls_key = opts.tls_key;
174        #[cfg(feature = "tls")]
175        let mut https_redirect = opts.https_redirect;
176        #[cfg(feature = "tls")]
177        let mut https_redirect_host = opts.https_redirect_host;
178        #[cfg(feature = "tls")]
179        let mut https_redirect_from_port = opts.https_redirect_from_port;
180        #[cfg(feature = "tls")]
181        let mut https_redirect_from_hosts = opts.https_redirect_from_hosts;
182        #[cfg(feature = "http2")]
183        let mut http2 = opts.http2;
184
185        let mut security_headers = opts.security_headers;
186        let mut cors_allow_origins = opts.cors_allow_origins;
187        let mut cors_allow_headers = opts.cors_allow_headers;
188        let mut cors_expose_headers = opts.cors_expose_headers;
189
190        #[cfg(feature = "directory-listing")]
191        let mut directory_listing = opts.directory_listing;
192        #[cfg(feature = "directory-listing")]
193        let mut directory_listing_order = opts.directory_listing_order;
194        #[cfg(feature = "directory-listing")]
195        let mut directory_listing_format = opts.directory_listing_format;
196
197        #[cfg(feature = "directory-listing-download")]
198        let mut directory_listing_download = opts.directory_listing_download;
199
200        #[cfg(feature = "basic-auth")]
201        let mut basic_auth = opts.basic_auth;
202
203        let mut fd = opts.fd;
204        #[cfg(unix)]
205        let mut unix_socket = opts.unix_socket.clone();
206        #[cfg(unix)]
207        let mut unix_socket_mode = opts.unix_socket_mode;
208        #[cfg(unix)]
209        let mut unix_socket_force = opts.unix_socket_force;
210        let mut threads_multiplier = opts.threads_multiplier;
211        let mut max_blocking_threads = opts.max_blocking_threads;
212        let mut grace_period = opts.grace_period;
213
214        #[cfg(feature = "fallback-page")]
215        let mut page_fallback = opts.page_fallback;
216
217        let mut log_remote_address = opts.log_remote_address;
218        let mut log_x_real_ip = opts.log_x_real_ip;
219        let mut log_forwarded_for = opts.log_forwarded_for;
220        let mut trusted_proxies = opts.trusted_proxies;
221        let mut redirect_trailing_slash = opts.redirect_trailing_slash;
222        let mut include_hidden = opts.include_hidden;
223        let mut follow_symlinks = opts.follow_symlinks;
224        let mut use_relative_root = opts.use_relative_root;
225        let mut accept_markdown = opts.accept_markdown;
226        let mut text_charset = opts.text_charset;
227        let mut index_files = opts.index_files;
228        let mut health = opts.health;
229
230        #[cfg(feature = "metrics")]
231        let mut metrics = opts.metrics;
232
233        let mut maintenance_mode = opts.maintenance_mode;
234        let mut maintenance_mode_status = opts.maintenance_mode_status;
235        let mut maintenance_mode_file = opts.maintenance_mode_file;
236
237        // Windows-only options
238        #[cfg(windows)]
239        let mut windows_service = opts.windows_service;
240
241        // Define the advanced file options
242        let mut settings_advanced: Option<Advanced> = None;
243
244        let to_use_config_file = match Path::new("./config.toml").is_file() {
245            true => {
246                eprintln!(
247                    "Deprecated: 'config.toml' found, rename it to 'sws.toml' to prepare for future releases"
248                );
249                PathBuf::from("./config.toml")
250            }
251            false => opts.config_file.clone(),
252        };
253
254        if let Some((settings, config_file_resolved)) = read_file_settings(&to_use_config_file)? {
255            config_file = config_file_resolved;
256
257            // File-based "general" options
258            let has_general_settings = settings.general.is_some();
259            if let Some(general) = settings.general {
260                if let Some(v) = general.host {
261                    host = v
262                }
263                if let Some(v) = general.port {
264                    port = v
265                }
266                if let Some(v) = general.root {
267                    root = v
268                }
269                if let Some(ref v) = general.log_level {
270                    log_level = v.name().to_lowercase();
271                }
272                if let Some(v) = general.log_with_ansi {
273                    log_with_ansi = v;
274                }
275                if let Some(v) = general.log_format {
276                    log_format = v;
277                }
278                if let Some(v) = general.log_file {
279                    log_file = Some(v);
280                }
281                if let Some(v) = general.cache_control_headers {
282                    cache_control_headers = v
283                }
284                if let Some(v) = general.etag {
285                    etag = v
286                }
287                #[cfg(any(
288                    feature = "compression",
289                    feature = "compression-gzip",
290                    feature = "compression-brotli",
291                    feature = "compression-zstd",
292                    feature = "compression-deflate"
293                ))]
294                if let Some(v) = general.compression {
295                    compression = v
296                }
297                #[cfg(any(
298                    feature = "compression",
299                    feature = "compression-gzip",
300                    feature = "compression-brotli",
301                    feature = "compression-zstd",
302                    feature = "compression-deflate"
303                ))]
304                if let Some(v) = general.compression_level {
305                    compression_level = v
306                }
307                if let Some(v) = general.compression_static {
308                    compression_static = v
309                }
310                if let Some(v) = general.page404 {
311                    page404 = v
312                }
313                if let Some(v) = general.page50x {
314                    page50x = v
315                }
316                #[cfg(feature = "tls")]
317                if let Some(v) = general.tls {
318                    tls = v
319                }
320                #[cfg(feature = "tls")]
321                if let Some(v) = general.tls_cert {
322                    tls_cert = Some(v)
323                }
324                #[cfg(feature = "tls")]
325                if let Some(v) = general.tls_key {
326                    tls_key = Some(v)
327                }
328                #[cfg(feature = "http2")]
329                if let Some(v) = general.http2 {
330                    http2 = v
331                }
332                #[cfg(feature = "tls")]
333                if let Some(v) = general.https_redirect {
334                    https_redirect = v
335                }
336                #[cfg(feature = "tls")]
337                if let Some(v) = general.https_redirect_host {
338                    https_redirect_host = v
339                }
340                #[cfg(feature = "tls")]
341                if let Some(v) = general.https_redirect_from_port {
342                    https_redirect_from_port = v
343                }
344                #[cfg(feature = "tls")]
345                if let Some(v) = general.https_redirect_from_hosts {
346                    https_redirect_from_hosts = v
347                }
348                #[cfg(feature = "tls")]
349                match general.security_headers {
350                    Some(v) => security_headers = v,
351                    _ => {
352                        if tls {
353                            security_headers = true;
354                        }
355                    }
356                }
357                #[cfg(not(feature = "tls"))]
358                if let Some(v) = general.security_headers {
359                    security_headers = v
360                }
361                if let Some(ref v) = general.cors_allow_origins {
362                    v.clone_into(&mut cors_allow_origins)
363                }
364                if let Some(ref v) = general.cors_allow_headers {
365                    v.clone_into(&mut cors_allow_headers)
366                }
367                if let Some(ref v) = general.cors_expose_headers {
368                    v.clone_into(&mut cors_expose_headers)
369                }
370                #[cfg(feature = "directory-listing")]
371                if let Some(v) = general.directory_listing {
372                    directory_listing = v
373                }
374                #[cfg(feature = "directory-listing")]
375                if let Some(v) = general.directory_listing_order {
376                    directory_listing_order = v
377                }
378                #[cfg(feature = "directory-listing")]
379                if let Some(v) = general.directory_listing_format {
380                    directory_listing_format = v
381                }
382                #[cfg(feature = "directory-listing-download")]
383                if let Some(v) = general.directory_listing_download {
384                    directory_listing_download = v
385                }
386                #[cfg(feature = "basic-auth")]
387                if let Some(ref v) = general.basic_auth {
388                    v.clone_into(&mut basic_auth)
389                }
390                if let Some(v) = general.fd {
391                    fd = Some(v)
392                }
393                #[cfg(unix)]
394                if let Some(v) = general.unix_socket {
395                    unix_socket = Some(v)
396                }
397                #[cfg(unix)]
398                if let Some(v) = general.unix_socket_mode {
399                    unix_socket_mode = Some(v)
400                }
401                #[cfg(unix)]
402                if let Some(v) = general.unix_socket_force {
403                    unix_socket_force = v
404                }
405                if let Some(v) = general.threads_multiplier {
406                    threads_multiplier = v
407                }
408                if let Some(v) = general.max_blocking_threads {
409                    max_blocking_threads = v
410                }
411                if let Some(v) = general.grace_period {
412                    grace_period = v
413                }
414                #[cfg(feature = "fallback-page")]
415                if let Some(v) = general.page_fallback {
416                    page_fallback = v
417                }
418                if let Some(v) = general.log_remote_address {
419                    log_remote_address = v
420                }
421                if let Some(v) = general.log_x_real_ip {
422                    log_x_real_ip = v
423                }
424                if let Some(v) = general.log_forwarded_for {
425                    log_forwarded_for = v
426                }
427                if let Some(v) = general.trusted_proxies {
428                    trusted_proxies = v
429                }
430                if let Some(v) = general.redirect_trailing_slash {
431                    redirect_trailing_slash = v
432                }
433                if let Some(v) = general.include_hidden {
434                    include_hidden = v
435                }
436                if let Some(v) = general.follow_symlinks {
437                    follow_symlinks = v
438                }
439                if let Some(v) = general.use_relative_root {
440                    use_relative_root = v
441                }
442                if let Some(v) = general.health {
443                    health = v
444                }
445                if let Some(v) = general.accept_markdown {
446                    accept_markdown = v
447                }
448                if let Some(v) = general.text_charset {
449                    text_charset = v
450                }
451                #[cfg(feature = "metrics")]
452                if let Some(v) = general.metrics {
453                    metrics = v
454                }
455                if let Some(v) = general.index_files {
456                    index_files = v
457                }
458                if let Some(v) = general.maintenance_mode {
459                    maintenance_mode = v
460                }
461                if let Some(v) = general.maintenance_mode_status {
462                    maintenance_mode_status =
463                        StatusCode::from_u16(v).with_context(|| "invalid HTTP status code")?
464                }
465                if let Some(v) = general.maintenance_mode_file {
466                    maintenance_mode_file = v
467                }
468
469                // Windows-only options
470                #[cfg(windows)]
471                if let Some(v) = general.windows_service {
472                    windows_service = v
473                }
474            }
475
476            // Logging system initialization in config file context
477            if log_init {
478                logger::init(
479                    log_level.as_str(),
480                    &log_format,
481                    log_with_ansi,
482                    log_file.as_deref(),
483                )?;
484            }
485
486            tracing::debug!("config file read successfully");
487            tracing::debug!("config file path provided: {}", opts.config_file.display());
488            tracing::debug!("config file path resolved: {}", config_file.display());
489
490            if !has_general_settings {
491                tracing::warn!(
492                    "config file empty or no `general` settings found, using default values"
493                );
494            }
495
496            // File-based "advanced" options
497            if let Some(advanced) = settings.advanced {
498                // 1. Custom HTTP headers assignment
499                let headers_entries = match advanced.headers {
500                    Some(headers_entries) => {
501                        let mut headers_vec: Vec<Headers> = Vec::new();
502
503                        // Compile a glob pattern for each header sources entry
504                        for headers_entry in headers_entries.iter() {
505                            let source = Glob::new(&headers_entry.source)
506                                .with_context(|| {
507                                    format!(
508                                        "can not compile glob pattern for header source: {}",
509                                        headers_entry.source
510                                    )
511                                })?
512                                .compile_matcher();
513
514                            headers_vec.push(Headers {
515                                source,
516                                headers: headers_entry.headers.to_owned(),
517                            });
518                        }
519                        Some(headers_vec)
520                    }
521                    _ => None,
522                };
523
524                // 2. Rewrites assignment
525                let rewrites_entries = match advanced.rewrites {
526                    Some(rewrites_entries) => {
527                        let mut rewrites_vec: Vec<Rewrites> = Vec::new();
528
529                        // Compile a glob pattern for each rewrite sources entry
530                        for rewrites_entry in rewrites_entries.iter() {
531                            let source = GlobBuilder::new(&rewrites_entry.source)
532                                .literal_separator(true)
533                                .build()
534                                .with_context(|| {
535                                    format!(
536                                        "can not compile glob pattern for rewrite source: {}",
537                                        rewrites_entry.source
538                                    )
539                                })?
540                                .compile_matcher();
541
542                            let pattern = source
543                                .glob()
544                                .regex()
545                                .trim_start_matches("(?-u)")
546                                .replace("?:.*", ".*")
547                                .replace("?:", "")
548                                .replace(".*.*", ".*")
549                                .to_owned();
550                            tracing::debug!(
551                                "url rewrites glob pattern: {}",
552                                &rewrites_entry.source
553                            );
554                            tracing::debug!("url rewrites regex equivalent: {}", pattern);
555
556                            let source = Regex::new(&pattern).with_context(|| {
557                                    format!(
558                                        "can not compile regex pattern equivalent for rewrite source: {}",
559                                        pattern
560                                    )
561                                })?;
562
563                            let replacer = build_placeholder_replacer(&source);
564                            rewrites_vec.push(Rewrites {
565                                source,
566                                destination: rewrites_entry.destination.to_owned(),
567                                redirect: rewrites_entry.redirect.to_owned(),
568                                replacer,
569                            });
570                        }
571                        Some(rewrites_vec)
572                    }
573                    _ => None,
574                };
575
576                // 3. Redirects assignment
577                let redirects_entries = match advanced.redirects {
578                    Some(redirects_entries) => {
579                        let mut redirects_vec: Vec<Redirects> = Vec::new();
580
581                        // Compile a glob pattern for each redirect sources entry
582                        for redirects_entry in redirects_entries.iter() {
583                            let source = GlobBuilder::new(&redirects_entry.source)
584                                .literal_separator(true)
585                                .build()
586                                .with_context(|| {
587                                    format!(
588                                        "can not compile glob pattern for redirect source: {}",
589                                        redirects_entry.source
590                                    )
591                                })?
592                                .compile_matcher();
593
594                            let pattern = source
595                                .glob()
596                                .regex()
597                                .trim_start_matches("(?-u)")
598                                .replace("?:.*", ".*")
599                                .replace("?:", "")
600                                .replace(".*.*", ".*")
601                                .to_owned();
602                            tracing::debug!(
603                                "url redirects glob pattern: {}",
604                                &redirects_entry.source
605                            );
606                            tracing::debug!("url redirects regex equivalent: {}", pattern);
607
608                            let source = Regex::new(&pattern).with_context(|| {
609                                    format!(
610                                        "can not compile regex pattern equivalent for redirect source: {}",
611                                        pattern
612                                    )
613                                })?;
614
615                            let status_code = redirects_entry.kind.to_owned() as u16;
616                            let replacer = build_placeholder_replacer(&source);
617                            redirects_vec.push(Redirects {
618                                host: redirects_entry.host.to_owned(),
619                                source,
620                                destination: redirects_entry.destination.to_owned(),
621                                kind: StatusCode::from_u16(status_code).with_context(|| {
622                                    format!("invalid redirect status code: {status_code}")
623                                })?,
624                                replacer,
625                            });
626                        }
627                        Some(redirects_vec)
628                    }
629                    _ => None,
630                };
631
632                // 3. Virtual hosts assignment
633                let vhosts_entries = match advanced.virtual_hosts {
634                    Some(vhosts_entries) => {
635                        let mut vhosts_vec: Vec<VirtualHosts> = Vec::new();
636
637                        for vhosts_entry in vhosts_entries.iter() {
638                            if let Some(root) = vhosts_entry.root.to_owned() {
639                                // Make sure path is valid
640                                let root_dir = helpers::get_valid_dirpath(&root)
641                                    .with_context(|| "root directory for virtual host was not found or inaccessible")?;
642                                // Canonicalize once so the per-request
643                                // containment check can skip a `canonicalize`
644                                // syscall (see `static_files::security`), unless
645                                // `use_relative_root` is enabled.
646                                let root_dir = if use_relative_root {
647                                    root_dir
648                                } else {
649                                    root_dir.canonicalize().unwrap_or(root_dir)
650                                };
651                                tracing::debug!(
652                                    "added virtual host: {} -> {}",
653                                    vhosts_entry.host,
654                                    root_dir.display()
655                                );
656                                vhosts_vec.push(VirtualHosts {
657                                    host: vhosts_entry.host.to_owned(),
658                                    root: root_dir,
659                                });
660                            }
661                        }
662                        Some(vhosts_vec)
663                    }
664                    _ => None,
665                };
666
667                settings_advanced = Some(Advanced {
668                    headers: headers_entries,
669                    rewrites: rewrites_entries,
670                    redirects: redirects_entries,
671                    virtual_hosts: vhosts_entries,
672                    memory_cache: advanced.memory_cache,
673                });
674            }
675        } else if log_init {
676            // Logging system initialization on demand
677            logger::init(
678                log_level.as_str(),
679                &log_format,
680                log_with_ansi,
681                log_file.as_deref(),
682            )?;
683        }
684
685        // Runtime validation: HTTP/2 requires TLS
686        #[cfg(all(feature = "http2", feature = "tls"))]
687        if http2 && !tls {
688            bail!("HTTP/2 requires TLS; enable --tls along with --tls-cert and --tls-key");
689        }
690
691        // Runtime validation: ANSI log output requires pretty format
692        if log_with_ansi && log_format != logger::LogFormat::Pretty {
693            bail!("--log-with-ansi requires --log-format=pretty");
694        }
695
696        // Runtime validation: HTTPS redirect requires TLS
697        #[cfg(feature = "tls")]
698        if https_redirect && !tls {
699            bail!("--https-redirect requires TLS to be enabled (--tls)");
700        }
701
702        // Auto-enable security headers when TLS is on (applies when no config file is present)
703        #[cfg(feature = "tls")]
704        if tls && !security_headers {
705            security_headers = true;
706        }
707
708        Ok(Settings {
709            general: General {
710                version,
711                host,
712                port,
713                root,
714                log_level,
715                log_with_ansi,
716                log_format,
717                log_file,
718                config_file,
719                cache_control_headers,
720                etag,
721                #[cfg(any(
722                    feature = "compression",
723                    feature = "compression-gzip",
724                    feature = "compression-brotli",
725                    feature = "compression-zstd",
726                    feature = "compression-deflate"
727                ))]
728                compression,
729                #[cfg(any(
730                    feature = "compression",
731                    feature = "compression-gzip",
732                    feature = "compression-brotli",
733                    feature = "compression-zstd",
734                    feature = "compression-deflate"
735                ))]
736                compression_level,
737                compression_static,
738                page404,
739                page50x,
740                #[cfg(feature = "http2")]
741                http2,
742                #[cfg(feature = "tls")]
743                tls,
744                #[cfg(feature = "tls")]
745                tls_cert,
746                #[cfg(feature = "tls")]
747                tls_key,
748                #[cfg(feature = "tls")]
749                https_redirect,
750                #[cfg(feature = "tls")]
751                https_redirect_host,
752                #[cfg(feature = "tls")]
753                https_redirect_from_port,
754                #[cfg(feature = "tls")]
755                https_redirect_from_hosts,
756                security_headers,
757                cors_allow_origins,
758                cors_allow_headers,
759                cors_expose_headers,
760                #[cfg(feature = "directory-listing")]
761                directory_listing,
762                #[cfg(feature = "directory-listing")]
763                directory_listing_order,
764                #[cfg(feature = "directory-listing")]
765                directory_listing_format,
766                #[cfg(feature = "directory-listing-download")]
767                directory_listing_download,
768                #[cfg(feature = "basic-auth")]
769                basic_auth,
770                fd,
771                #[cfg(unix)]
772                unix_socket,
773                #[cfg(unix)]
774                unix_socket_mode,
775                #[cfg(unix)]
776                unix_socket_force,
777                threads_multiplier,
778                max_blocking_threads,
779                grace_period,
780                #[cfg(feature = "fallback-page")]
781                page_fallback,
782                log_remote_address,
783                log_x_real_ip,
784                log_forwarded_for,
785                trusted_proxies,
786                redirect_trailing_slash,
787                include_hidden,
788                follow_symlinks,
789                use_relative_root,
790                accept_markdown,
791                text_charset,
792                index_files,
793                health,
794                #[cfg(feature = "metrics")]
795                metrics,
796                maintenance_mode,
797                maintenance_mode_status,
798                maintenance_mode_file,
799
800                // Windows-only options and commands
801                #[cfg(windows)]
802                windows_service,
803                commands: opts.commands,
804            },
805            advanced: settings_advanced,
806        })
807    }
808}
809
810fn read_file_settings(config_file: &Path) -> Result<Option<(FileSettings, PathBuf)>> {
811    if config_file.is_file() {
812        let file_path_resolved = config_file
813            .canonicalize()
814            .with_context(|| "unable to resolve toml config file path")?;
815
816        let settings = FileSettings::read(&file_path_resolved).with_context(
817            || "unable to read toml config file because has invalid format or unsupported options",
818        )?;
819
820        return Ok(Some((settings, file_path_resolved)));
821    }
822    Ok(None)
823}