// SPDX-License-Identifier: MIT OR Apache-2.0 // This file is part of Static Web Server. // See https://static-web-server.net/ for more information // Copyright (C) 2019-present Jose Quintana //! The server configuration file options (manifest) use headers::HeaderMap; use serde::Deserialize; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::path::Path; use std::{collections::BTreeSet, path::PathBuf}; #[cfg(feature = "directory-listing")] use crate::directory_listing::DirListFmt; use crate::{helpers, Context, Result}; #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] /// Log level variants. pub enum LogLevel { /// Error log level. Error, /// Warn log level. Warn, /// Info log level. Info, /// Debug log level. Debug, /// Trace log level. Trace, } impl LogLevel { /// Get log level name. pub fn name(&self) -> &'static str { match self { LogLevel::Error => "error", LogLevel::Warn => "warn", LogLevel::Info => "info", LogLevel::Debug => "debug", LogLevel::Trace => "trace", } } } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] /// Represents an HTTP headers map. pub struct Headers { /// Header source. pub source: String, #[serde(rename(deserialize = "headers"), with = "http_serde::header_map")] /// headers list. pub headers: HeaderMap, } #[derive(Debug, Serialize_repr, Deserialize_repr, Clone)] #[repr(u16)] /// Represents redirects types. pub enum RedirectsKind { /// Moved Permanently Permanent = 301, /// Found Temporary = 302, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] /// Represents redirects types. pub struct Redirects { /// Optional host to match against an incoming URI host if specified pub host: Option, /// Source of the redirect. pub source: String, /// Redirect destination. pub destination: String, /// Redirect type either 301 (Moved Permanently) or 302 (Found). pub kind: RedirectsKind, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] /// Represents rewrites types. pub struct Rewrites { /// Source of the rewrite. pub source: String, /// Rewrite destination. pub destination: String, /// Optional redirect type either 301 (Moved Permanently) or 302 (Found). pub redirect: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] /// Represents virtual hosts with different root directories pub struct VirtualHosts { /// The value to check for in the "Host" header pub host: String, /// The root directory for this virtual host pub root: Option, } /// Advanced server options only available in configuration file mode. #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct Advanced { /// Headers pub headers: Option>, /// Rewrites pub rewrites: Option>, /// Redirects pub redirects: Option>, /// Name-based virtual hosting pub virtual_hosts: Option>, } /// General server options available in configuration file mode. /// Note that the `--config-file` option is excluded from itself. #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct General { /// Server address. pub host: Option, /// Server port. pub port: Option, /// Root directory path. pub root: Option, /// Logging. pub log_level: Option, /// Cache Control headers. pub cache_control_headers: Option, /// Compression. #[cfg(feature = "compression")] #[cfg_attr(docsrs, doc(cfg(feature = "compression")))] pub compression: Option, /// Check for a pre-compressed file on disk. #[cfg(feature = "compression")] #[cfg_attr(docsrs, doc(cfg(feature = "compression")))] pub compression_static: Option, /// Error 404 pages. pub page404: Option, /// Error 50x pages. pub page50x: Option, /// HTTP/2 + TLS. #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub http2: Option, /// Http2 tls certificate feature. #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub http2_tls_cert: Option, /// Http2 tls key feature. #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub http2_tls_key: Option, /// Redirect all HTTP requests to HTTPS. #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub https_redirect: Option, /// HTTP host port where the redirect server will listen for requests to redirect them to HTTPS. #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub https_redirect_host: Option, /// Host port for redirecting HTTP requests to HTTPS. #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub https_redirect_from_port: Option, /// List of host names or IPs allowed to redirect from. #[cfg(feature = "http2")] #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub https_redirect_from_hosts: Option, /// Security headers. pub security_headers: Option, /// Cors allow origins feature. pub cors_allow_origins: Option, /// Cors allow headers feature. pub cors_allow_headers: Option, /// Cors expose headers feature. pub cors_expose_headers: Option, /// List of files to be used as an index for requests ending with the slash character (‘/’). pub index_files: Option, /// Directory listing feature. #[cfg(feature = "directory-listing")] #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))] pub directory_listing: Option, /// Directory listing order feature. #[cfg(feature = "directory-listing")] #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))] pub directory_listing_order: Option, /// Directory listing format feature. #[cfg(feature = "directory-listing")] #[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))] pub directory_listing_format: Option, /// Basic Authentication feature. #[cfg(feature = "basic-auth")] #[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))] pub basic_auth: Option, /// File descriptor binding feature. pub fd: Option, /// Worker threads. pub threads_multiplier: Option, /// Max blocking threads feature. pub max_blocking_threads: Option, /// Grace period feature. pub grace_period: Option, /// Page fallback feature. #[cfg(feature = "fallback-page")] #[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))] pub page_fallback: Option, /// Log remote address feature. pub log_remote_address: Option, /// Log remote user agent feature. pub log_remote_user_agent: Option, /// Redirect trailing slash feature. pub redirect_trailing_slash: Option, /// Ignore hidden files feature. pub ignore_hidden_files: Option, /// Health endpoint feature. pub health: Option, #[cfg(all(unix, feature = "experimental"))] /// Metrics endpoint feature (experimental). pub experimental_metrics: Option, /// Maintenance mode feature. pub maintenance_mode: Option, /// Custom HTTP status for when entering into maintenance mode. pub maintenance_mode_status: Option, /// Custom maintenance mode HTML file. pub maintenance_mode_file: Option, #[cfg(windows)] /// windows service feature. pub windows_service: Option, } /// Full server configuration #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct Settings { /// General settings. pub general: Option, /// Advanced settings. pub advanced: Option, } impl Settings { /// Read and deserialize the server TOML configuration file by path. pub fn read(config_file: &Path) -> Result { // Validate TOML file extension let ext = config_file.extension(); if ext.is_none() || ext.unwrap().is_empty() || ext.unwrap().ne("toml") { bail!("configuration file should be in toml format. E.g `config.toml`"); } // TODO: validate minimal TOML file structure needed let toml = read_toml_file(config_file).with_context(|| "error reading toml configuration file")?; let mut unused = BTreeSet::new(); let manifest: Settings = serde_ignored::deserialize(toml, |path| { let mut key = String::new(); helpers::stringify(&mut key, &path); unused.insert(key); }) .with_context(|| "error during toml configuration file deserialization")?; for key in unused { println!("Warning: unused configuration manifest key \"{key}\" or unsupported"); } Ok(manifest) } } /// Read and parse a TOML file from an specific path. fn read_toml_file(path: &Path) -> Result { let toml_str = helpers::read_file(path).with_context(|| { format!( "error trying to deserialize toml configuration file at \"{}\"", path.display() ) })?; toml_str .parse() .map_err(|e| anyhow::Error::from(e).context("could not parse input as TOML")) }