web / ferron /src /util /validate_config.rs
victorgeek's picture
Upload folder using huggingface_hub
9552aa0 verified
use crate::ferron_common::ServerConfig;
use hyper::header::{HeaderName, HeaderValue};
use std::collections::HashSet;
use std::error::Error;
use std::net::IpAddr;
use std::str::FromStr;
use yaml_rust2::{yaml, Yaml};
// Struct to store used configuration properties
struct UsedProperties<'a> {
config: &'a ServerConfig,
properties: HashSet<String>,
}
impl<'a> UsedProperties<'a> {
fn new(config: &'a ServerConfig) -> Self {
UsedProperties {
config,
properties: HashSet::new(),
}
}
fn contains(&mut self, property: &str) -> bool {
self.properties.insert(property.to_string());
!self.config[property].is_badvalue()
}
fn unused(&self) -> Vec<String> {
let empty_hashmap = yaml::Hash::new();
let all_properties = self
.config
.as_hash()
.unwrap_or(&empty_hashmap)
.keys()
.filter_map(|a| a.as_str().map(|a| a.to_string()));
all_properties
.filter(|item| !self.properties.contains(item))
.collect()
}
}
fn validate_ip(ip: &str) -> bool {
let _: IpAddr = match ip.parse() {
Ok(addr) => addr,
Err(_) => return false,
};
true
}
// Internal configuration file validators
pub fn validate_config(
config: ServerConfig,
is_global: bool,
is_location: bool,
modules_optional_builtin: &[String],
) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
let mut used_properties = UsedProperties::new(&config);
let domain_badvalue = !used_properties.contains("domain");
let ip_badvalue = !used_properties.contains("ip");
if !domain_badvalue && config["domain"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid domain name"))?
}
if !ip_badvalue {
match config["ip"].as_str() {
Some(ip) => {
if !validate_ip(ip) {
Err(anyhow::anyhow!("Invalid IP address"))?;
}
}
None => {
Err(anyhow::anyhow!("Invalid IP address"))?;
}
}
}
if domain_badvalue && ip_badvalue && !is_global && !is_location {
Err(anyhow::anyhow!(
"A host must either have IP address or domain name specified"
))?;
}
if used_properties.contains("path") {
if !is_location {
Err(anyhow::anyhow!(
"Location path configuration is only allowed in location configuration"
))?;
}
if config["path"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid location path"))?;
}
}
if used_properties.contains("locations") && is_location {
Err(anyhow::anyhow!("Nested locations are not allowed"))?;
}
if used_properties.contains("loadModules") {
if !is_global {
Err(anyhow::anyhow!(
"Module configuration is not allowed in host configuration"
))?
}
if let Some(modules) = config["loadModules"].as_vec() {
let modules_iter = modules.iter();
for module_name_yaml in modules_iter {
if module_name_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid module name"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid module configuration"))?
}
}
if used_properties.contains("port") {
if !is_global {
Err(anyhow::anyhow!(
"HTTP port configuration is not allowed in host configuration"
))?
}
if let Some(port) = config["port"].as_i64() {
if !(0..=65535).contains(&port) {
Err(anyhow::anyhow!("Invalid HTTP port"))?
}
} else if config["port"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid HTTP port"))?
}
}
if used_properties.contains("sport") {
if !is_global {
Err(anyhow::anyhow!(
"HTTPS port configuration is not allowed in host configuration"
))?
}
if let Some(port) = config["sport"].as_i64() {
if !(0..=65535).contains(&port) {
Err(anyhow::anyhow!("Invalid HTTPS port"))?
}
} else if config["sport"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid HTTPS port"))?
}
}
if used_properties.contains("secure") {
if !is_global {
Err(anyhow::anyhow!(
"HTTPS enabling configuration is not allowed in host configuration"
))?
}
if config["secure"].as_bool().is_none() {
Err(anyhow::anyhow!("Invalid HTTPS enabling option value"))?
}
}
if used_properties.contains("enableHTTP2") {
if !is_global {
Err(anyhow::anyhow!(
"HTTP/2 enabling configuration is not allowed in host configuration"
))?
}
if config["enableHTTP2"].as_bool().is_none() {
Err(anyhow::anyhow!("Invalid HTTP/2 enabling option value"))?
}
}
if used_properties.contains("enableHTTP3") {
if !is_global {
Err(anyhow::anyhow!(
"HTTP/3 enabling configuration is not allowed in host configuration"
))?
}
if config["enableHTTP3"].as_bool().is_none() {
Err(anyhow::anyhow!("Invalid HTTP/3 enabling option value"))?
}
}
if used_properties.contains("logFilePath") {
if !is_global {
Err(anyhow::anyhow!(
"Log file configuration is not allowed in host configuration"
))?
}
if config["logFilePath"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid log file path"))?
}
}
if used_properties.contains("errorLogFilePath") {
if !is_global {
Err(anyhow::anyhow!(
"Error log file configuration is not allowed in host configuration"
))?
}
if config["errorLogFilePath"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid error log file path"))?
}
}
if used_properties.contains("cert") {
if !is_global {
Err(anyhow::anyhow!(
"TLS certificate configuration is not allowed in host configuration"
))?
}
if config["cert"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid TLS certificate path"))?
}
}
if used_properties.contains("key") {
if !is_global {
Err(anyhow::anyhow!(
"Private key configuration is not allowed in host configuration"
))?
}
if config["key"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid private key path"))?
}
}
if used_properties.contains("sni") {
if !is_global {
Err(anyhow::anyhow!(
"SNI configuration is not allowed in host configuration"
))?
}
if let Some(sni) = config["sni"].as_hash() {
let sni_hostnames = sni.keys();
for sni_hostname_unknown in sni_hostnames {
if let Some(sni_hostname) = sni_hostname_unknown.as_str() {
if sni[sni_hostname_unknown]["cert"].as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid SNI TLS certificate path for \"{}\"",
sni_hostname
))?
}
if sni[sni_hostname_unknown]["key"].as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid SNI private key certificate path for \"{}\"",
sni_hostname
))?
}
} else {
Err(anyhow::anyhow!("Invalid SNI hostname"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid SNI certificate list"))?
}
}
if used_properties.contains("http2Options") {
if !is_global {
Err(anyhow::anyhow!(
"HTTP/2 configuration is not allowed in host configuration"
))?
}
if config["http2Options"].as_hash().is_some() {
if let Some(initial_window_size) = config["http2Options"]["initialWindowSize"].as_i64() {
if !(0..=2_147_483_647).contains(&initial_window_size) {
Err(anyhow::anyhow!("Invalid HTTP/2 initial window size"))?
}
}
if let Some(max_frame_size) = config["http2Options"]["maxFrameSize"].as_i64() {
if !(16_384..=16_777_215).contains(&max_frame_size) {
Err(anyhow::anyhow!("Invalid HTTP/2 max frame size"))?
}
}
if let Some(max_concurrent_streams) = config["http2Options"]["maxConcurrentStreams"].as_i64()
{
if max_concurrent_streams < 0 {
Err(anyhow::anyhow!("Invalid HTTP/2 max concurrent streams"))?
}
}
if let Some(max_header_list_size) = config["http2Options"]["maxHeaderListSize"].as_i64() {
if max_header_list_size < 0 {
Err(anyhow::anyhow!("Invalid HTTP/2 max header list size"))?
}
}
if !config["http2Options"]["enableConnectProtocol"].is_badvalue()
&& config["http2Options"]["enableConnectProtocol"]
.as_bool()
.is_none()
{
Err(anyhow::anyhow!(
"Invalid HTTP/2 enable connect protocol option"
))?
}
} else {
Err(anyhow::anyhow!("Invalid HTTP/2 options"))?
}
}
if used_properties.contains("useClientCertificate") {
if !is_global {
Err(anyhow::anyhow!(
"Client certificate verfication enabling option is not allowed in host configuration"
))?
}
if config["useClientCertificate"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid client certificate verification enabling option value"
))?
}
}
if used_properties.contains("cipherSuite") {
if !is_global {
Err(anyhow::anyhow!(
"Cipher suite configuration is not allowed in host configuration"
))?
}
if let Some(cipher_suites) = config["cipherSuite"].as_vec() {
let cipher_suites_iter = cipher_suites.iter();
for cipher_suite_name_yaml in cipher_suites_iter {
if cipher_suite_name_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid cipher suite"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid cipher suite configuration"))?
}
}
if used_properties.contains("ecdhCurve") {
if !is_global {
Err(anyhow::anyhow!(
"ECDH curve configuration is not allowed in host configuration"
))?
}
if let Some(ecdh_curves) = config["ecdhCurve"].as_vec() {
let ecdh_curves_iter = ecdh_curves.iter();
for ecdh_curve_name_yaml in ecdh_curves_iter {
if ecdh_curve_name_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid ECDH curve"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid ECDH curve configuration"))?
}
}
if used_properties.contains("tlsMinVersion") {
if !is_global {
Err(anyhow::anyhow!(
"Minimum TLS version is not allowed in host configuration"
))?
}
if config["tlsMinVersion"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid minimum TLS version"))?
}
}
if used_properties.contains("tlsMaxVersion") {
if !is_global {
Err(anyhow::anyhow!(
"Maximum TLS version is not allowed in host configuration"
))?
}
if config["tlsMaxVersion"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid maximum TLS version"))?
}
}
if used_properties.contains("enableOCSPStapling") {
if !is_global {
Err(anyhow::anyhow!(
"OCSP stapling enabling option is not allowed in host configuration"
))?
}
if config["enableOCSPStapling"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid OCSP stapling enabling option value"
))?
}
}
if used_properties.contains("serverAdministratorEmail")
&& config["serverAdministratorEmail"].as_str().is_none()
{
Err(anyhow::anyhow!(
"Invalid server administrator email address"
))?
}
if used_properties.contains("enableIPSpoofing") && config["enableIPSpoofing"].as_bool().is_none()
{
Err(anyhow::anyhow!(
"Invalid X-Forwarded-For enabling option value"
))?
}
if used_properties.contains("disableNonEncryptedServer") {
if !is_global {
Err(anyhow::anyhow!(
"Non-encrypted server disabling option is not allowed in host configuration"
))?
}
if config["disableNonEncryptedServer"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid non-encrypted server disabling option value"
))?
}
}
if used_properties.contains("blocklist") {
if !is_global {
Err(anyhow::anyhow!(
"Block list configuration is not allowed in host configuration"
))?
}
if let Some(blocklist) = config["blocklist"].as_vec() {
let blocklist_iter = blocklist.iter();
for blocklist_entry_yaml in blocklist_iter {
match blocklist_entry_yaml.as_str() {
Some(blocklist_entry) => {
if !validate_ip(blocklist_entry) {
Err(anyhow::anyhow!("Invalid block list entry"))?
}
}
None => Err(anyhow::anyhow!("Invalid block list entry"))?,
}
}
} else {
Err(anyhow::anyhow!("Invalid block list configuration"))?
}
}
if used_properties.contains("environmentVariables") {
if !is_global {
Err(anyhow::anyhow!(
"Environment variable configuration is not allowed in host configuration"
))?
}
if let Some(environment_variables_hash) = config["environmentVariables"].as_hash() {
let environment_variables_hash_iter = environment_variables_hash.iter();
for (var_name, var_value) in environment_variables_hash_iter {
if var_name.as_str().is_none() || var_value.as_str().is_none() {
Err(anyhow::anyhow!("Invalid environment variables"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid environment variables"))?
}
}
if used_properties.contains("disableToHTTPSRedirect")
&& config["disableToHTTPSRedirect"].as_bool().is_none()
{
Err(anyhow::anyhow!(
"Invalid HTTP to HTTPS redirect disabling option value"
))?
}
if used_properties.contains("wwwredirect") && config["wwwredirect"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid to \"www.\" URL redirect disabling option value"
))?
}
if used_properties.contains("customHeaders") {
if let Some(custom_headers_hash) = config["customHeaders"].as_hash() {
let custom_headers_hash_iter = custom_headers_hash.iter();
for (header_name, header_value) in custom_headers_hash_iter {
if let Some(header_name) = header_name.as_str() {
if let Some(header_value) = header_value.as_str() {
if HeaderValue::from_str(header_value).is_err()
|| HeaderName::from_str(header_name).is_err()
{
Err(anyhow::anyhow!("Invalid custom headers"))?
}
} else {
Err(anyhow::anyhow!("Invalid custom headers"))?
}
} else {
Err(anyhow::anyhow!("Invalid custom headers"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid custom headers"))?
}
}
if used_properties.contains("rewriteMap") {
if let Some(rewrite_map) = config["rewriteMap"].as_vec() {
let rewrite_map_iter = rewrite_map.iter();
for rewrite_map_entry_yaml in rewrite_map_iter {
if !rewrite_map_entry_yaml.is_hash() {
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
if rewrite_map_entry_yaml["regex"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
if rewrite_map_entry_yaml["replacement"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
if !rewrite_map_entry_yaml["isNotFile"].is_badvalue()
&& rewrite_map_entry_yaml["isNotFile"].as_bool().is_none()
{
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
if !rewrite_map_entry_yaml["isNotDirectory"].is_badvalue()
&& rewrite_map_entry_yaml["isNotDirectory"].as_bool().is_none()
{
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
if !rewrite_map_entry_yaml["allowDoubleSlashes"].is_badvalue()
&& rewrite_map_entry_yaml["allowDoubleSlashes"]
.as_bool()
.is_none()
{
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
if !rewrite_map_entry_yaml["last"].is_badvalue()
&& rewrite_map_entry_yaml["last"].as_bool().is_none()
{
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid URL rewrite map"))?
}
}
if used_properties.contains("enableRewriteLogging")
&& config["enableRewriteLogging"].as_bool().is_none()
{
Err(anyhow::anyhow!(
"Invalid URL rewrite logging enabling option value"
))?
}
if used_properties.contains("disableTrailingSlashRedirects")
&& config["disableTrailingSlashRedirects"].as_bool().is_none()
{
Err(anyhow::anyhow!(
"Invalid trailing slash redirect disabling option value"
))?
}
if used_properties.contains("users") {
if let Some(users) = config["users"].as_vec() {
let users_iter = users.iter();
for user_yaml in users_iter {
if !user_yaml.is_hash() {
Err(anyhow::anyhow!("Invalid user configuration"))?
}
if user_yaml["name"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid user configuration"))?
}
if user_yaml["pass"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid user configuration"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid user configuration"))?
}
}
if used_properties.contains("nonStandardCodes") {
if let Some(non_standard_codes) = config["nonStandardCodes"].as_vec() {
let non_standard_codes_iter = non_standard_codes.iter();
for non_standard_code_yaml in non_standard_codes_iter {
if !non_standard_code_yaml.is_hash() {
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
if non_standard_code_yaml["scode"].as_i64().is_none() {
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
if !non_standard_code_yaml["regex"].is_badvalue()
&& non_standard_code_yaml["regex"].as_str().is_none()
{
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
if !non_standard_code_yaml["url"].is_badvalue()
&& non_standard_code_yaml["url"].as_str().is_none()
{
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
if non_standard_code_yaml["regex"].is_badvalue()
&& non_standard_code_yaml["url"].is_badvalue()
{
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
if !non_standard_code_yaml["realm"].is_badvalue()
&& non_standard_code_yaml["realm"].as_str().is_none()
{
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
if !non_standard_code_yaml["disableBruteProtection"].is_badvalue()
&& non_standard_code_yaml["disableBruteProtection"]
.as_bool()
.is_none()
{
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
if !non_standard_code_yaml["userList"].is_badvalue() {
if let Some(users) = non_standard_code_yaml["userList"].as_vec() {
let users_iter = users.iter();
for user_yaml in users_iter {
if user_yaml.as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
}
} else {
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
}
if !non_standard_code_yaml["users"].is_badvalue() {
if let Some(users) = non_standard_code_yaml["users"].as_vec() {
let users_iter = users.iter();
for user_yaml in users_iter {
match user_yaml.as_str() {
Some(user) => {
if !validate_ip(user) {
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
}
None => Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?,
}
}
} else {
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
}
}
} else {
Err(anyhow::anyhow!(
"Invalid non-standard status code configuration"
))?
}
}
if used_properties.contains("errorPages") {
if let Some(error_pages) = config["errorPages"].as_vec() {
let error_pages_iter = error_pages.iter();
for error_page_yaml in error_pages_iter {
if !error_page_yaml.is_hash() {
Err(anyhow::anyhow!("Invalid custom error page configuration"))?
}
if error_page_yaml["scode"].as_i64().is_none() {
Err(anyhow::anyhow!("Invalid custom error page configuration"))?
}
if error_page_yaml["path"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid custom error page configuration"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid custom error page configuration"))?
}
}
if used_properties.contains("wwwroot") && config["wwwroot"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid webroot"))?
}
if used_properties.contains("enableETag") && config["enableETag"].as_bool().is_none() {
Err(anyhow::anyhow!("Invalid ETag enabling option"))?
}
if used_properties.contains("enableCompression")
&& config["enableCompression"].as_bool().is_none()
{
Err(anyhow::anyhow!("Invalid HTTP compression enabling option"))?
}
if used_properties.contains("enableDirectoryListing")
&& config["enableDirectoryListing"].as_bool().is_none()
{
Err(anyhow::anyhow!("Invalid directory listing enabling option"))?
}
if used_properties.contains("enableAutomaticTLS") {
if !is_global {
Err(anyhow::anyhow!(
"Automatic TLS enabling configuration is not allowed in host configuration"
))?
}
if config["enableAutomaticTLS"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid automatic TLS enabling option value"
))?
}
}
if used_properties.contains("useAutomaticTLSHTTPChallenge") {
if !is_global {
Err(anyhow::anyhow!(
"Automatic TLS HTTP challenge enabling configuration is not allowed in host configuration"
))?
}
if config["useAutomaticTLSHTTPChallenge"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid automatic TLS HTTP challenge enabling option value"
))?
}
}
if used_properties.contains("automaticTLSContactEmail") {
if !is_global {
Err(anyhow::anyhow!(
"Automatic TLS contact email address configuration is not allowed in host configuration"
))?
}
if config["automaticTLSContactEmail"].as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid automatic TLS contact email address"
))?
}
}
if used_properties.contains("automaticTLSContactCacheDirectory") {
if !is_global {
Err(anyhow::anyhow!(
"Automatic TLS cache directory configuration is not allowed in host configuration"
))?
}
if config["automaticTLSContactCacheDirectory"]
.as_str()
.is_none()
{
Err(anyhow::anyhow!(
"Invalid automatic TLS cache directory path"
))?
}
}
if used_properties.contains("automaticTLSLetsEncryptProduction") {
if !is_global {
Err(anyhow::anyhow!(
"Let's Encrypt production endpoint for automatic TLS enabling configuration is not allowed in host configuration"
))?
}
if config["automaticTLSLetsEncryptProduction"]
.as_bool()
.is_none()
{
Err(anyhow::anyhow!(
"Invalid Let's Encrypt production endpoint for automatic TLS enabling option value"
))?
}
}
if used_properties.contains("timeout") {
if !is_global {
Err(anyhow::anyhow!(
"Server timeout configuration is not allowed in host configuration"
))?
}
if !config["timeout"].is_null() {
if let Some(maximum_cache_response_size) = config["timeout"].as_i64() {
if maximum_cache_response_size < 0 {
Err(anyhow::anyhow!("Invalid server timeout"))?
}
} else {
Err(anyhow::anyhow!("Invalid server timeout"))?
}
}
}
for module_optional_builtin in modules_optional_builtin.iter() {
match module_optional_builtin as &str {
#[cfg(feature = "rproxy")]
"rproxy" => {
if used_properties.contains("proxyTo") {
if let Some(proxy_urls) = config["proxyTo"].as_vec() {
let proxy_urls_iter = proxy_urls.iter();
for proxy_url_yaml in proxy_urls_iter {
if proxy_url_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid reverse proxy target URL value"))?
}
}
} else if config["proxyTo"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid reverse proxy target URL value"))?
}
}
if used_properties.contains("secureProxyTo") {
if let Some(proxy_urls) = config["secureProxyTo"].as_vec() {
let proxy_urls_iter = proxy_urls.iter();
for proxy_url_yaml in proxy_urls_iter {
if proxy_url_yaml.as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid secure reverse proxy target URL value"
))?
}
}
} else if config["secureProxyTo"].as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid secure reverse proxy target URL value"
))?
}
}
if used_properties.contains("enableLoadBalancerHealthCheck")
&& config["enableLoadBalancerHealthCheck"].as_bool().is_none()
{
Err(anyhow::anyhow!(
"Invalid load balancer health check enabling option value"
))?
}
if used_properties.contains("loadBalancerHealthCheckMaximumFails") {
if let Some(window) = config["loadBalancerHealthCheckMaximumFails"].as_i64() {
if window < 0 {
Err(anyhow::anyhow!(
"Invalid load balancer health check maximum fails value"
))?
}
} else {
Err(anyhow::anyhow!(
"Invalid load balancer health check maximum fails value"
))?
}
}
if used_properties.contains("loadBalancerHealthCheckWindow") {
if !is_global {
Err(anyhow::anyhow!(
"Load balancer health check window configuration is not allowed in host configuration"
))?
}
if let Some(window) = config["loadBalancerHealthCheckWindow"].as_i64() {
if window < 0 {
Err(anyhow::anyhow!(
"Invalid load balancer health check window value"
))?
}
} else {
Err(anyhow::anyhow!(
"Invalid load balancer health check window value"
))?
}
}
if used_properties.contains("disableProxyCertificateVerification")
&& config["disableProxyCertificateVerification"]
.as_bool()
.is_none()
{
Err(anyhow::anyhow!(
"Invalid proxy certificate verification disabling option value"
))?
}
}
#[cfg(feature = "cache")]
"cache" => {
if used_properties.contains("cacheVaryHeaders") {
if let Some(modules) = config["cacheVaryHeaders"].as_vec() {
let modules_iter = modules.iter();
for module_name_yaml in modules_iter {
if module_name_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid varying cache header"))?
}
}
} else {
Err(anyhow::anyhow!(
"Invalid varying cache headers configuration"
))?
}
}
if used_properties.contains("cacheIgnoreHeaders") {
if let Some(modules) = config["cacheIgnoreHeaders"].as_vec() {
let modules_iter = modules.iter();
for module_name_yaml in modules_iter {
if module_name_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid ignored cache header"))?
}
}
} else {
Err(anyhow::anyhow!(
"Invalid ignored cache headers configuration"
))?
}
}
if used_properties.contains("maximumCacheResponseSize")
&& !config["maximumCacheResponseSize"].is_null()
{
if let Some(maximum_cache_response_size) = config["maximumCacheResponseSize"].as_i64() {
if maximum_cache_response_size < 0 {
Err(anyhow::anyhow!("Invalid maximum cache response size"))?
}
} else {
Err(anyhow::anyhow!("Invalid maximum cache response size"))?
}
}
if used_properties.contains("maximumCacheEntries") {
if !is_global {
Err(anyhow::anyhow!(
"Maximum cache entries configuration is not allowed in host configuration"
))?
}
if !config["maximumCacheEntries"].is_null() {
if let Some(maximum_cache_response_size) = config["maximumCacheEntries"].as_i64() {
if maximum_cache_response_size < 0 {
Err(anyhow::anyhow!("Invalid maximum cache entries"))?
}
} else {
Err(anyhow::anyhow!("Invalid maximum cache entries"))?
}
}
}
}
#[cfg(feature = "cgi")]
"cgi" => {
if used_properties.contains("cgiScriptExtensions") {
if let Some(cgi_script_extensions) = config["cgiScriptExtensions"].as_vec() {
let cgi_script_extensions_iter = cgi_script_extensions.iter();
for cgi_script_extension_yaml in cgi_script_extensions_iter {
if cgi_script_extension_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid CGI script extension"))?
}
}
} else {
Err(anyhow::anyhow!(
"Invalid CGI script extension configuration"
))?
}
}
if used_properties.contains("cgiScriptInterpreters") {
if let Some(cgi_script_interpreters) = config["cgiScriptInterpreters"].as_hash() {
for (cgi_script_interpreter_extension_unknown, cgi_script_interpreter_params_unknown) in
cgi_script_interpreters.iter()
{
if cgi_script_interpreter_extension_unknown.as_str().is_some() {
if !cgi_script_interpreter_params_unknown.is_null() {
if let Some(cgi_script_interpreter_params) =
cgi_script_interpreter_params_unknown.as_vec()
{
let cgi_script_interpreter_params_iter = cgi_script_interpreter_params.iter();
for cgi_script_interpreter_param_yaml in cgi_script_interpreter_params_iter {
if cgi_script_interpreter_param_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid CGI script interpreter parameter"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid CGI script interpreter parameters"))?
}
}
} else {
Err(anyhow::anyhow!("Invalid CGI script interpreter extension"))?
}
}
} else {
Err(anyhow::anyhow!(
"Invalid CGI script interpreter configuration"
))?
}
}
}
#[cfg(feature = "scgi")]
"scgi" => {
if used_properties.contains("scgiTo") && config["scgiTo"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid SCGI target URL value"))?
}
if used_properties.contains("scgiPath") && config["scgiPath"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid SCGI path"))?
}
}
#[cfg(feature = "fcgi")]
"fcgi" => {
if used_properties.contains("fcgiScriptExtensions") {
if let Some(fastcgi_script_extensions) = config["fcgiScriptExtensions"].as_vec() {
let fastcgi_script_extensions_iter = fastcgi_script_extensions.iter();
for fastcgi_script_extension_yaml in fastcgi_script_extensions_iter {
if fastcgi_script_extension_yaml.as_str().is_none() {
Err(anyhow::anyhow!("Invalid CGI script extension"))?
}
}
} else {
Err(anyhow::anyhow!(
"Invalid CGI script extension configuration"
))?
}
}
if used_properties.contains("fcgiTo") && config["fcgiTo"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid FastCGI target URL value"))?
}
if used_properties.contains("fcgiPath") && config["fcgiPath"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid FastCGI path"))?
}
}
#[cfg(feature = "fauth")]
"fauth" => {
if used_properties.contains("authTo") && config["authTo"].as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid forwarded authentication target URL value"
))?
}
if used_properties.contains("forwardedAuthCopyHeaders") {
if let Some(modules) = config["forwardedAuthCopyHeaders"].as_vec() {
let modules_iter = modules.iter();
for module_name_yaml in modules_iter {
if module_name_yaml.as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid forwarded authentication response header to copy"
))?
}
}
} else {
Err(anyhow::anyhow!(
"Invalid forwarded authentication response headers to copy configuration"
))?
}
}
}
#[cfg(feature = "wsgi")]
"wsgi" => {
if used_properties.contains("wsgiApplicationPath")
&& config["wsgiApplicationPath"].as_str().is_none()
{
Err(anyhow::anyhow!("Invalid path to the WSGI application"))?
}
if used_properties.contains("wsgiPath") && config["wsgiPath"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid WSGI request base path"))?
}
if used_properties.contains("wsgiClearModuleImportPath") {
if !is_global {
Err(anyhow::anyhow!(
"WSGI Python module import path clearing option is not allowed in host configuration"
))?
}
if config["wsgiClearModuleImportPath"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid WSGI Python module import path clearing option value"
))?
}
}
}
#[cfg(feature = "wsgid")]
"wsgid" => {
if used_properties.contains("wsgidApplicationPath")
&& config["wsgidApplicationPath"].as_str().is_none()
{
Err(anyhow::anyhow!(
"Invalid path to the WSGI (with pre-forked process pool) application"
))?
}
if used_properties.contains("wsgidPath") && config["wsgidPath"].as_str().is_none() {
Err(anyhow::anyhow!(
"Invalid WSGI (with pre-forked process pool) request base path"
))?
}
}
#[cfg(feature = "asgi")]
"asgi" => {
if used_properties.contains("asgiApplicationPath")
&& config["asgiApplicationPath"].as_str().is_none()
{
Err(anyhow::anyhow!("Invalid path to the ASGI application"))?
}
if used_properties.contains("asgiPath") && config["asgiPath"].as_str().is_none() {
Err(anyhow::anyhow!("Invalid ASGI request base path"))?
}
if used_properties.contains("asgiClearModuleImportPath") {
if !is_global {
Err(anyhow::anyhow!(
"ASGI Python module import path clearing option is not allowed in host configuration"
))?
}
if config["asgiClearModuleImportPath"].as_bool().is_none() {
Err(anyhow::anyhow!(
"Invalid ASGI Python module import path clearing option value"
))?
}
}
}
_ => (),
}
}
Ok(used_properties.unused())
}
pub fn prepare_config_for_validation(
config: &Yaml,
) -> Result<impl Iterator<Item = (Yaml, bool, bool)>, Box<dyn Error + Send + Sync>> {
let mut vector = Vec::new();
if let Some(global_config) = config["global"].as_hash() {
let global_config_yaml = Yaml::Hash(global_config.clone());
vector.push(global_config_yaml);
}
let mut vector2 = Vec::new();
let mut vector3 = Vec::new();
if !config["hosts"].is_badvalue() {
if let Some(hosts) = config["hosts"].as_vec() {
for host in hosts.iter() {
if !host["locations"].is_badvalue() {
if let Some(locations) = host["locations"].as_vec() {
vector3.append(&mut locations.clone());
} else {
return Err(anyhow::anyhow!("Invalid location configuration").into());
}
}
}
vector2 = hosts.clone();
} else {
return Err(anyhow::anyhow!("Invalid virtual host configuration").into());
}
}
let iter = vector
.into_iter()
.map(|item| (item, true, false))
.chain(vector2.into_iter().map(|item| (item, false, false)))
.chain(vector3.into_iter().map(|item| (item, false, true)));
Ok(iter)
}