ferron/util/
validate_config.rs

1use crate::ferron_common::ServerConfig;
2use hyper::header::{HeaderName, HeaderValue};
3use std::collections::HashSet;
4use std::error::Error;
5use std::net::IpAddr;
6use std::str::FromStr;
7use yaml_rust2::{yaml, Yaml};
8
9// Struct to store used configuration properties
10struct UsedProperties<'a> {
11  config: &'a ServerConfig,
12  properties: HashSet<String>,
13}
14
15impl<'a> UsedProperties<'a> {
16  fn new(config: &'a ServerConfig) -> Self {
17    UsedProperties {
18      config,
19      properties: HashSet::new(),
20    }
21  }
22
23  fn contains(&mut self, property: &str) -> bool {
24    self.properties.insert(property.to_string());
25    !self.config[property].is_badvalue()
26  }
27
28  fn unused(&self) -> Vec<String> {
29    let empty_hashmap = yaml::Hash::new();
30    let all_properties = self
31      .config
32      .as_hash()
33      .unwrap_or(&empty_hashmap)
34      .keys()
35      .filter_map(|a| a.as_str().map(|a| a.to_string()));
36    all_properties
37      .filter(|item| !self.properties.contains(item))
38      .collect()
39  }
40}
41
42fn validate_ip(ip: &str) -> bool {
43  let _: IpAddr = match ip.parse() {
44    Ok(addr) => addr,
45    Err(_) => return false,
46  };
47  true
48}
49
50// Internal configuration file validators
51pub fn validate_config(
52  config: ServerConfig,
53  is_global: bool,
54  is_location: bool,
55  modules_optional_builtin: &[String],
56) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
57  let mut used_properties = UsedProperties::new(&config);
58
59  let domain_badvalue = !used_properties.contains("domain");
60  let ip_badvalue = !used_properties.contains("ip");
61
62  if !domain_badvalue && config["domain"].as_str().is_none() {
63    Err(anyhow::anyhow!("Invalid domain name"))?
64  }
65
66  if !ip_badvalue {
67    match config["ip"].as_str() {
68      Some(ip) => {
69        if !validate_ip(ip) {
70          Err(anyhow::anyhow!("Invalid IP address"))?;
71        }
72      }
73      None => {
74        Err(anyhow::anyhow!("Invalid IP address"))?;
75      }
76    }
77  }
78
79  if domain_badvalue && ip_badvalue && !is_global && !is_location {
80    Err(anyhow::anyhow!(
81      "A host must either have IP address or domain name specified"
82    ))?;
83  }
84
85  if used_properties.contains("path") {
86    if !is_location {
87      Err(anyhow::anyhow!(
88        "Location path configuration is only allowed in location configuration"
89      ))?;
90    }
91    if config["path"].as_str().is_none() {
92      Err(anyhow::anyhow!("Invalid location path"))?;
93    }
94  }
95
96  if used_properties.contains("locations") && is_location {
97    Err(anyhow::anyhow!("Nested locations are not allowed"))?;
98  }
99
100  if used_properties.contains("loadModules") {
101    if !is_global {
102      Err(anyhow::anyhow!(
103        "Module configuration is not allowed in host configuration"
104      ))?
105    }
106    if let Some(modules) = config["loadModules"].as_vec() {
107      let modules_iter = modules.iter();
108      for module_name_yaml in modules_iter {
109        if module_name_yaml.as_str().is_none() {
110          Err(anyhow::anyhow!("Invalid module name"))?
111        }
112      }
113    } else {
114      Err(anyhow::anyhow!("Invalid module configuration"))?
115    }
116  }
117
118  if used_properties.contains("port") {
119    if !is_global {
120      Err(anyhow::anyhow!(
121        "HTTP port configuration is not allowed in host configuration"
122      ))?
123    }
124    if let Some(port) = config["port"].as_i64() {
125      if !(0..=65535).contains(&port) {
126        Err(anyhow::anyhow!("Invalid HTTP port"))?
127      }
128    } else if config["port"].as_str().is_none() {
129      Err(anyhow::anyhow!("Invalid HTTP port"))?
130    }
131  }
132
133  if used_properties.contains("sport") {
134    if !is_global {
135      Err(anyhow::anyhow!(
136        "HTTPS port configuration is not allowed in host configuration"
137      ))?
138    }
139    if let Some(port) = config["sport"].as_i64() {
140      if !(0..=65535).contains(&port) {
141        Err(anyhow::anyhow!("Invalid HTTPS port"))?
142      }
143    } else if config["sport"].as_str().is_none() {
144      Err(anyhow::anyhow!("Invalid HTTPS port"))?
145    }
146  }
147
148  if used_properties.contains("secure") {
149    if !is_global {
150      Err(anyhow::anyhow!(
151        "HTTPS enabling configuration is not allowed in host configuration"
152      ))?
153    }
154    if config["secure"].as_bool().is_none() {
155      Err(anyhow::anyhow!("Invalid HTTPS enabling option value"))?
156    }
157  }
158
159  if used_properties.contains("enableHTTP2") {
160    if !is_global {
161      Err(anyhow::anyhow!(
162        "HTTP/2 enabling configuration is not allowed in host configuration"
163      ))?
164    }
165    if config["enableHTTP2"].as_bool().is_none() {
166      Err(anyhow::anyhow!("Invalid HTTP/2 enabling option value"))?
167    }
168  }
169
170  if used_properties.contains("enableHTTP3") {
171    if !is_global {
172      Err(anyhow::anyhow!(
173        "HTTP/3 enabling configuration is not allowed in host configuration"
174      ))?
175    }
176    if config["enableHTTP3"].as_bool().is_none() {
177      Err(anyhow::anyhow!("Invalid HTTP/3 enabling option value"))?
178    }
179  }
180
181  if used_properties.contains("logFilePath") {
182    if !is_global {
183      Err(anyhow::anyhow!(
184        "Log file configuration is not allowed in host configuration"
185      ))?
186    }
187    if config["logFilePath"].as_str().is_none() {
188      Err(anyhow::anyhow!("Invalid log file path"))?
189    }
190  }
191
192  if used_properties.contains("errorLogFilePath") {
193    if !is_global {
194      Err(anyhow::anyhow!(
195        "Error log file configuration is not allowed in host configuration"
196      ))?
197    }
198    if config["errorLogFilePath"].as_str().is_none() {
199      Err(anyhow::anyhow!("Invalid error log file path"))?
200    }
201  }
202
203  if used_properties.contains("cert") {
204    if !is_global {
205      Err(anyhow::anyhow!(
206        "TLS certificate configuration is not allowed in host configuration"
207      ))?
208    }
209    if config["cert"].as_str().is_none() {
210      Err(anyhow::anyhow!("Invalid TLS certificate path"))?
211    }
212  }
213
214  if used_properties.contains("key") {
215    if !is_global {
216      Err(anyhow::anyhow!(
217        "Private key configuration is not allowed in host configuration"
218      ))?
219    }
220    if config["key"].as_str().is_none() {
221      Err(anyhow::anyhow!("Invalid private key path"))?
222    }
223  }
224
225  if used_properties.contains("sni") {
226    if !is_global {
227      Err(anyhow::anyhow!(
228        "SNI configuration is not allowed in host configuration"
229      ))?
230    }
231    if let Some(sni) = config["sni"].as_hash() {
232      let sni_hostnames = sni.keys();
233      for sni_hostname_unknown in sni_hostnames {
234        if let Some(sni_hostname) = sni_hostname_unknown.as_str() {
235          if sni[sni_hostname_unknown]["cert"].as_str().is_none() {
236            Err(anyhow::anyhow!(
237              "Invalid SNI TLS certificate path for \"{}\"",
238              sni_hostname
239            ))?
240          }
241          if sni[sni_hostname_unknown]["key"].as_str().is_none() {
242            Err(anyhow::anyhow!(
243              "Invalid SNI private key certificate path for \"{}\"",
244              sni_hostname
245            ))?
246          }
247        } else {
248          Err(anyhow::anyhow!("Invalid SNI hostname"))?
249        }
250      }
251    } else {
252      Err(anyhow::anyhow!("Invalid SNI certificate list"))?
253    }
254  }
255
256  if used_properties.contains("http2Options") {
257    if !is_global {
258      Err(anyhow::anyhow!(
259        "HTTP/2 configuration is not allowed in host configuration"
260      ))?
261    }
262    if config["http2Options"].as_hash().is_some() {
263      if let Some(initial_window_size) = config["http2Options"]["initialWindowSize"].as_i64() {
264        if !(0..=2_147_483_647).contains(&initial_window_size) {
265          Err(anyhow::anyhow!("Invalid HTTP/2 initial window size"))?
266        }
267      }
268
269      if let Some(max_frame_size) = config["http2Options"]["maxFrameSize"].as_i64() {
270        if !(16_384..=16_777_215).contains(&max_frame_size) {
271          Err(anyhow::anyhow!("Invalid HTTP/2 max frame size"))?
272        }
273      }
274
275      if let Some(max_concurrent_streams) = config["http2Options"]["maxConcurrentStreams"].as_i64()
276      {
277        if max_concurrent_streams < 0 {
278          Err(anyhow::anyhow!("Invalid HTTP/2 max concurrent streams"))?
279        }
280      }
281
282      if let Some(max_header_list_size) = config["http2Options"]["maxHeaderListSize"].as_i64() {
283        if max_header_list_size < 0 {
284          Err(anyhow::anyhow!("Invalid HTTP/2 max header list size"))?
285        }
286      }
287
288      if !config["http2Options"]["enableConnectProtocol"].is_badvalue()
289        && config["http2Options"]["enableConnectProtocol"]
290          .as_bool()
291          .is_none()
292      {
293        Err(anyhow::anyhow!(
294          "Invalid HTTP/2 enable connect protocol option"
295        ))?
296      }
297    } else {
298      Err(anyhow::anyhow!("Invalid HTTP/2 options"))?
299    }
300  }
301
302  if used_properties.contains("useClientCertificate") {
303    if !is_global {
304      Err(anyhow::anyhow!(
305        "Client certificate verfication enabling option is not allowed in host configuration"
306      ))?
307    }
308    if config["useClientCertificate"].as_bool().is_none() {
309      Err(anyhow::anyhow!(
310        "Invalid client certificate verification enabling option value"
311      ))?
312    }
313  }
314
315  if used_properties.contains("cipherSuite") {
316    if !is_global {
317      Err(anyhow::anyhow!(
318        "Cipher suite configuration is not allowed in host configuration"
319      ))?
320    }
321    if let Some(cipher_suites) = config["cipherSuite"].as_vec() {
322      let cipher_suites_iter = cipher_suites.iter();
323      for cipher_suite_name_yaml in cipher_suites_iter {
324        if cipher_suite_name_yaml.as_str().is_none() {
325          Err(anyhow::anyhow!("Invalid cipher suite"))?
326        }
327      }
328    } else {
329      Err(anyhow::anyhow!("Invalid cipher suite configuration"))?
330    }
331  }
332
333  if used_properties.contains("ecdhCurve") {
334    if !is_global {
335      Err(anyhow::anyhow!(
336        "ECDH curve configuration is not allowed in host configuration"
337      ))?
338    }
339    if let Some(ecdh_curves) = config["ecdhCurve"].as_vec() {
340      let ecdh_curves_iter = ecdh_curves.iter();
341      for ecdh_curve_name_yaml in ecdh_curves_iter {
342        if ecdh_curve_name_yaml.as_str().is_none() {
343          Err(anyhow::anyhow!("Invalid ECDH curve"))?
344        }
345      }
346    } else {
347      Err(anyhow::anyhow!("Invalid ECDH curve configuration"))?
348    }
349  }
350
351  if used_properties.contains("tlsMinVersion") {
352    if !is_global {
353      Err(anyhow::anyhow!(
354        "Minimum TLS version is not allowed in host configuration"
355      ))?
356    }
357    if config["tlsMinVersion"].as_str().is_none() {
358      Err(anyhow::anyhow!("Invalid minimum TLS version"))?
359    }
360  }
361
362  if used_properties.contains("tlsMaxVersion") {
363    if !is_global {
364      Err(anyhow::anyhow!(
365        "Maximum TLS version is not allowed in host configuration"
366      ))?
367    }
368    if config["tlsMaxVersion"].as_str().is_none() {
369      Err(anyhow::anyhow!("Invalid maximum TLS version"))?
370    }
371  }
372
373  if used_properties.contains("enableOCSPStapling") {
374    if !is_global {
375      Err(anyhow::anyhow!(
376        "OCSP stapling enabling option is not allowed in host configuration"
377      ))?
378    }
379    if config["enableOCSPStapling"].as_bool().is_none() {
380      Err(anyhow::anyhow!(
381        "Invalid OCSP stapling enabling option value"
382      ))?
383    }
384  }
385
386  if used_properties.contains("serverAdministratorEmail")
387    && config["serverAdministratorEmail"].as_str().is_none()
388  {
389    Err(anyhow::anyhow!(
390      "Invalid server administrator email address"
391    ))?
392  }
393
394  if used_properties.contains("enableIPSpoofing") && config["enableIPSpoofing"].as_bool().is_none()
395  {
396    Err(anyhow::anyhow!(
397      "Invalid X-Forwarded-For enabling option value"
398    ))?
399  }
400
401  if used_properties.contains("disableNonEncryptedServer") {
402    if !is_global {
403      Err(anyhow::anyhow!(
404        "Non-encrypted server disabling option is not allowed in host configuration"
405      ))?
406    }
407    if config["disableNonEncryptedServer"].as_bool().is_none() {
408      Err(anyhow::anyhow!(
409        "Invalid non-encrypted server disabling option value"
410      ))?
411    }
412  }
413
414  if used_properties.contains("blocklist") {
415    if !is_global {
416      Err(anyhow::anyhow!(
417        "Block list configuration is not allowed in host configuration"
418      ))?
419    }
420    if let Some(blocklist) = config["blocklist"].as_vec() {
421      let blocklist_iter = blocklist.iter();
422      for blocklist_entry_yaml in blocklist_iter {
423        match blocklist_entry_yaml.as_str() {
424          Some(blocklist_entry) => {
425            if !validate_ip(blocklist_entry) {
426              Err(anyhow::anyhow!("Invalid block list entry"))?
427            }
428          }
429          None => Err(anyhow::anyhow!("Invalid block list entry"))?,
430        }
431      }
432    } else {
433      Err(anyhow::anyhow!("Invalid block list configuration"))?
434    }
435  }
436
437  if used_properties.contains("environmentVariables") {
438    if !is_global {
439      Err(anyhow::anyhow!(
440        "Environment variable configuration is not allowed in host configuration"
441      ))?
442    }
443    if let Some(environment_variables_hash) = config["environmentVariables"].as_hash() {
444      let environment_variables_hash_iter = environment_variables_hash.iter();
445      for (var_name, var_value) in environment_variables_hash_iter {
446        if var_name.as_str().is_none() || var_value.as_str().is_none() {
447          Err(anyhow::anyhow!("Invalid environment variables"))?
448        }
449      }
450    } else {
451      Err(anyhow::anyhow!("Invalid environment variables"))?
452    }
453  }
454
455  if used_properties.contains("disableToHTTPSRedirect")
456    && config["disableToHTTPSRedirect"].as_bool().is_none()
457  {
458    Err(anyhow::anyhow!(
459      "Invalid HTTP to HTTPS redirect disabling option value"
460    ))?
461  }
462
463  if used_properties.contains("wwwredirect") && config["wwwredirect"].as_bool().is_none() {
464    Err(anyhow::anyhow!(
465      "Invalid to \"www.\" URL redirect disabling option value"
466    ))?
467  }
468
469  if used_properties.contains("customHeaders") {
470    if let Some(custom_headers_hash) = config["customHeaders"].as_hash() {
471      let custom_headers_hash_iter = custom_headers_hash.iter();
472      for (header_name, header_value) in custom_headers_hash_iter {
473        if let Some(header_name) = header_name.as_str() {
474          if let Some(header_value) = header_value.as_str() {
475            if HeaderValue::from_str(header_value).is_err()
476              || HeaderName::from_str(header_name).is_err()
477            {
478              Err(anyhow::anyhow!("Invalid custom headers"))?
479            }
480          } else {
481            Err(anyhow::anyhow!("Invalid custom headers"))?
482          }
483        } else {
484          Err(anyhow::anyhow!("Invalid custom headers"))?
485        }
486      }
487    } else {
488      Err(anyhow::anyhow!("Invalid custom headers"))?
489    }
490  }
491
492  if used_properties.contains("rewriteMap") {
493    if let Some(rewrite_map) = config["rewriteMap"].as_vec() {
494      let rewrite_map_iter = rewrite_map.iter();
495      for rewrite_map_entry_yaml in rewrite_map_iter {
496        if !rewrite_map_entry_yaml.is_hash() {
497          Err(anyhow::anyhow!("Invalid URL rewrite map"))?
498        }
499        if rewrite_map_entry_yaml["regex"].as_str().is_none() {
500          Err(anyhow::anyhow!("Invalid URL rewrite map"))?
501        }
502        if rewrite_map_entry_yaml["replacement"].as_str().is_none() {
503          Err(anyhow::anyhow!("Invalid URL rewrite map"))?
504        }
505        if !rewrite_map_entry_yaml["isNotFile"].is_badvalue()
506          && rewrite_map_entry_yaml["isNotFile"].as_bool().is_none()
507        {
508          Err(anyhow::anyhow!("Invalid URL rewrite map"))?
509        }
510        if !rewrite_map_entry_yaml["isNotDirectory"].is_badvalue()
511          && rewrite_map_entry_yaml["isNotDirectory"].as_bool().is_none()
512        {
513          Err(anyhow::anyhow!("Invalid URL rewrite map"))?
514        }
515        if !rewrite_map_entry_yaml["allowDoubleSlashes"].is_badvalue()
516          && rewrite_map_entry_yaml["allowDoubleSlashes"]
517            .as_bool()
518            .is_none()
519        {
520          Err(anyhow::anyhow!("Invalid URL rewrite map"))?
521        }
522        if !rewrite_map_entry_yaml["last"].is_badvalue()
523          && rewrite_map_entry_yaml["last"].as_bool().is_none()
524        {
525          Err(anyhow::anyhow!("Invalid URL rewrite map"))?
526        }
527      }
528    } else {
529      Err(anyhow::anyhow!("Invalid URL rewrite map"))?
530    }
531  }
532
533  if used_properties.contains("enableRewriteLogging")
534    && config["enableRewriteLogging"].as_bool().is_none()
535  {
536    Err(anyhow::anyhow!(
537      "Invalid URL rewrite logging enabling option value"
538    ))?
539  }
540
541  if used_properties.contains("disableTrailingSlashRedirects")
542    && config["disableTrailingSlashRedirects"].as_bool().is_none()
543  {
544    Err(anyhow::anyhow!(
545      "Invalid trailing slash redirect disabling option value"
546    ))?
547  }
548
549  if used_properties.contains("users") {
550    if let Some(users) = config["users"].as_vec() {
551      let users_iter = users.iter();
552      for user_yaml in users_iter {
553        if !user_yaml.is_hash() {
554          Err(anyhow::anyhow!("Invalid user configuration"))?
555        }
556        if user_yaml["name"].as_str().is_none() {
557          Err(anyhow::anyhow!("Invalid user configuration"))?
558        }
559        if user_yaml["pass"].as_str().is_none() {
560          Err(anyhow::anyhow!("Invalid user configuration"))?
561        }
562      }
563    } else {
564      Err(anyhow::anyhow!("Invalid user configuration"))?
565    }
566  }
567
568  if used_properties.contains("nonStandardCodes") {
569    if let Some(non_standard_codes) = config["nonStandardCodes"].as_vec() {
570      let non_standard_codes_iter = non_standard_codes.iter();
571      for non_standard_code_yaml in non_standard_codes_iter {
572        if !non_standard_code_yaml.is_hash() {
573          Err(anyhow::anyhow!(
574            "Invalid non-standard status code configuration"
575          ))?
576        }
577        if non_standard_code_yaml["scode"].as_i64().is_none() {
578          Err(anyhow::anyhow!(
579            "Invalid non-standard status code configuration"
580          ))?
581        }
582        if !non_standard_code_yaml["regex"].is_badvalue()
583          && non_standard_code_yaml["regex"].as_str().is_none()
584        {
585          Err(anyhow::anyhow!(
586            "Invalid non-standard status code configuration"
587          ))?
588        }
589        if !non_standard_code_yaml["url"].is_badvalue()
590          && non_standard_code_yaml["url"].as_str().is_none()
591        {
592          Err(anyhow::anyhow!(
593            "Invalid non-standard status code configuration"
594          ))?
595        }
596        if non_standard_code_yaml["regex"].is_badvalue()
597          && non_standard_code_yaml["url"].is_badvalue()
598        {
599          Err(anyhow::anyhow!(
600            "Invalid non-standard status code configuration"
601          ))?
602        }
603        if !non_standard_code_yaml["realm"].is_badvalue()
604          && non_standard_code_yaml["realm"].as_str().is_none()
605        {
606          Err(anyhow::anyhow!(
607            "Invalid non-standard status code configuration"
608          ))?
609        }
610        if !non_standard_code_yaml["disableBruteProtection"].is_badvalue()
611          && non_standard_code_yaml["disableBruteProtection"]
612            .as_bool()
613            .is_none()
614        {
615          Err(anyhow::anyhow!(
616            "Invalid non-standard status code configuration"
617          ))?
618        }
619        if !non_standard_code_yaml["userList"].is_badvalue() {
620          if let Some(users) = non_standard_code_yaml["userList"].as_vec() {
621            let users_iter = users.iter();
622            for user_yaml in users_iter {
623              if user_yaml.as_str().is_none() {
624                Err(anyhow::anyhow!(
625                  "Invalid non-standard status code configuration"
626                ))?
627              }
628            }
629          } else {
630            Err(anyhow::anyhow!(
631              "Invalid non-standard status code configuration"
632            ))?
633          }
634        }
635        if !non_standard_code_yaml["users"].is_badvalue() {
636          if let Some(users) = non_standard_code_yaml["users"].as_vec() {
637            let users_iter = users.iter();
638            for user_yaml in users_iter {
639              match user_yaml.as_str() {
640                Some(user) => {
641                  if !validate_ip(user) {
642                    Err(anyhow::anyhow!(
643                      "Invalid non-standard status code configuration"
644                    ))?
645                  }
646                }
647                None => Err(anyhow::anyhow!(
648                  "Invalid non-standard status code configuration"
649                ))?,
650              }
651            }
652          } else {
653            Err(anyhow::anyhow!(
654              "Invalid non-standard status code configuration"
655            ))?
656          }
657        }
658      }
659    } else {
660      Err(anyhow::anyhow!(
661        "Invalid non-standard status code configuration"
662      ))?
663    }
664  }
665
666  if used_properties.contains("errorPages") {
667    if let Some(error_pages) = config["errorPages"].as_vec() {
668      let error_pages_iter = error_pages.iter();
669      for error_page_yaml in error_pages_iter {
670        if !error_page_yaml.is_hash() {
671          Err(anyhow::anyhow!("Invalid custom error page configuration"))?
672        }
673        if error_page_yaml["scode"].as_i64().is_none() {
674          Err(anyhow::anyhow!("Invalid custom error page configuration"))?
675        }
676        if error_page_yaml["path"].as_str().is_none() {
677          Err(anyhow::anyhow!("Invalid custom error page configuration"))?
678        }
679      }
680    } else {
681      Err(anyhow::anyhow!("Invalid custom error page configuration"))?
682    }
683  }
684
685  if used_properties.contains("wwwroot") && config["wwwroot"].as_str().is_none() {
686    Err(anyhow::anyhow!("Invalid webroot"))?
687  }
688
689  if used_properties.contains("enableETag") && config["enableETag"].as_bool().is_none() {
690    Err(anyhow::anyhow!("Invalid ETag enabling option"))?
691  }
692
693  if used_properties.contains("enableCompression")
694    && config["enableCompression"].as_bool().is_none()
695  {
696    Err(anyhow::anyhow!("Invalid HTTP compression enabling option"))?
697  }
698
699  if used_properties.contains("enableDirectoryListing")
700    && config["enableDirectoryListing"].as_bool().is_none()
701  {
702    Err(anyhow::anyhow!("Invalid directory listing enabling option"))?
703  }
704
705  if used_properties.contains("enableAutomaticTLS") {
706    if !is_global {
707      Err(anyhow::anyhow!(
708        "Automatic TLS enabling configuration is not allowed in host configuration"
709      ))?
710    }
711    if config["enableAutomaticTLS"].as_bool().is_none() {
712      Err(anyhow::anyhow!(
713        "Invalid automatic TLS enabling option value"
714      ))?
715    }
716  }
717
718  if used_properties.contains("useAutomaticTLSHTTPChallenge") {
719    if !is_global {
720      Err(anyhow::anyhow!(
721        "Automatic TLS HTTP challenge enabling configuration is not allowed in host configuration"
722      ))?
723    }
724    if config["useAutomaticTLSHTTPChallenge"].as_bool().is_none() {
725      Err(anyhow::anyhow!(
726        "Invalid automatic TLS HTTP challenge enabling option value"
727      ))?
728    }
729  }
730
731  if used_properties.contains("automaticTLSContactEmail") {
732    if !is_global {
733      Err(anyhow::anyhow!(
734        "Automatic TLS contact email address configuration is not allowed in host configuration"
735      ))?
736    }
737    if config["automaticTLSContactEmail"].as_str().is_none() {
738      Err(anyhow::anyhow!(
739        "Invalid automatic TLS contact email address"
740      ))?
741    }
742  }
743
744  if used_properties.contains("automaticTLSContactCacheDirectory") {
745    if !is_global {
746      Err(anyhow::anyhow!(
747        "Automatic TLS cache directory configuration is not allowed in host configuration"
748      ))?
749    }
750    if config["automaticTLSContactCacheDirectory"]
751      .as_str()
752      .is_none()
753    {
754      Err(anyhow::anyhow!(
755        "Invalid automatic TLS cache directory path"
756      ))?
757    }
758  }
759
760  if used_properties.contains("automaticTLSLetsEncryptProduction") {
761    if !is_global {
762      Err(anyhow::anyhow!(
763        "Let's Encrypt production endpoint for automatic TLS enabling configuration is not allowed in host configuration"
764      ))?
765    }
766    if config["automaticTLSLetsEncryptProduction"]
767      .as_bool()
768      .is_none()
769    {
770      Err(anyhow::anyhow!(
771        "Invalid Let's Encrypt production endpoint for automatic TLS enabling option value"
772      ))?
773    }
774  }
775
776  if used_properties.contains("timeout") {
777    if !is_global {
778      Err(anyhow::anyhow!(
779        "Server timeout configuration is not allowed in host configuration"
780      ))?
781    }
782    if !config["timeout"].is_null() {
783      if let Some(maximum_cache_response_size) = config["timeout"].as_i64() {
784        if maximum_cache_response_size < 0 {
785          Err(anyhow::anyhow!("Invalid server timeout"))?
786        }
787      } else {
788        Err(anyhow::anyhow!("Invalid server timeout"))?
789      }
790    }
791  }
792
793  for module_optional_builtin in modules_optional_builtin.iter() {
794    match module_optional_builtin as &str {
795      #[cfg(feature = "rproxy")]
796      "rproxy" => {
797        if used_properties.contains("proxyTo") {
798          if let Some(proxy_urls) = config["proxyTo"].as_vec() {
799            let proxy_urls_iter = proxy_urls.iter();
800            for proxy_url_yaml in proxy_urls_iter {
801              if proxy_url_yaml.as_str().is_none() {
802                Err(anyhow::anyhow!("Invalid reverse proxy target URL value"))?
803              }
804            }
805          } else if config["proxyTo"].as_str().is_none() {
806            Err(anyhow::anyhow!("Invalid reverse proxy target URL value"))?
807          }
808        }
809
810        if used_properties.contains("secureProxyTo") {
811          if let Some(proxy_urls) = config["secureProxyTo"].as_vec() {
812            let proxy_urls_iter = proxy_urls.iter();
813            for proxy_url_yaml in proxy_urls_iter {
814              if proxy_url_yaml.as_str().is_none() {
815                Err(anyhow::anyhow!(
816                  "Invalid secure reverse proxy target URL value"
817                ))?
818              }
819            }
820          } else if config["secureProxyTo"].as_str().is_none() {
821            Err(anyhow::anyhow!(
822              "Invalid secure reverse proxy target URL value"
823            ))?
824          }
825        }
826
827        if used_properties.contains("enableLoadBalancerHealthCheck")
828          && config["enableLoadBalancerHealthCheck"].as_bool().is_none()
829        {
830          Err(anyhow::anyhow!(
831            "Invalid load balancer health check enabling option value"
832          ))?
833        }
834
835        if used_properties.contains("loadBalancerHealthCheckMaximumFails") {
836          if let Some(window) = config["loadBalancerHealthCheckMaximumFails"].as_i64() {
837            if window < 0 {
838              Err(anyhow::anyhow!(
839                "Invalid load balancer health check maximum fails value"
840              ))?
841            }
842          } else {
843            Err(anyhow::anyhow!(
844              "Invalid load balancer health check maximum fails value"
845            ))?
846          }
847        }
848
849        if used_properties.contains("loadBalancerHealthCheckWindow") {
850          if !is_global {
851            Err(anyhow::anyhow!(
852              "Load balancer health check window configuration is not allowed in host configuration"
853            ))?
854          }
855          if let Some(window) = config["loadBalancerHealthCheckWindow"].as_i64() {
856            if window < 0 {
857              Err(anyhow::anyhow!(
858                "Invalid load balancer health check window value"
859              ))?
860            }
861          } else {
862            Err(anyhow::anyhow!(
863              "Invalid load balancer health check window value"
864            ))?
865          }
866        }
867
868        if used_properties.contains("disableProxyCertificateVerification")
869          && config["disableProxyCertificateVerification"]
870            .as_bool()
871            .is_none()
872        {
873          Err(anyhow::anyhow!(
874            "Invalid proxy certificate verification disabling option value"
875          ))?
876        }
877      }
878      #[cfg(feature = "cache")]
879      "cache" => {
880        if used_properties.contains("cacheVaryHeaders") {
881          if let Some(modules) = config["cacheVaryHeaders"].as_vec() {
882            let modules_iter = modules.iter();
883            for module_name_yaml in modules_iter {
884              if module_name_yaml.as_str().is_none() {
885                Err(anyhow::anyhow!("Invalid varying cache header"))?
886              }
887            }
888          } else {
889            Err(anyhow::anyhow!(
890              "Invalid varying cache headers configuration"
891            ))?
892          }
893        }
894
895        if used_properties.contains("cacheIgnoreHeaders") {
896          if let Some(modules) = config["cacheIgnoreHeaders"].as_vec() {
897            let modules_iter = modules.iter();
898            for module_name_yaml in modules_iter {
899              if module_name_yaml.as_str().is_none() {
900                Err(anyhow::anyhow!("Invalid ignored cache header"))?
901              }
902            }
903          } else {
904            Err(anyhow::anyhow!(
905              "Invalid ignored cache headers configuration"
906            ))?
907          }
908        }
909
910        if used_properties.contains("maximumCacheResponseSize")
911          && !config["maximumCacheResponseSize"].is_null()
912        {
913          if let Some(maximum_cache_response_size) = config["maximumCacheResponseSize"].as_i64() {
914            if maximum_cache_response_size < 0 {
915              Err(anyhow::anyhow!("Invalid maximum cache response size"))?
916            }
917          } else {
918            Err(anyhow::anyhow!("Invalid maximum cache response size"))?
919          }
920        }
921
922        if used_properties.contains("maximumCacheEntries") {
923          if !is_global {
924            Err(anyhow::anyhow!(
925              "Maximum cache entries configuration is not allowed in host configuration"
926            ))?
927          }
928          if !config["maximumCacheEntries"].is_null() {
929            if let Some(maximum_cache_response_size) = config["maximumCacheEntries"].as_i64() {
930              if maximum_cache_response_size < 0 {
931                Err(anyhow::anyhow!("Invalid maximum cache entries"))?
932              }
933            } else {
934              Err(anyhow::anyhow!("Invalid maximum cache entries"))?
935            }
936          }
937        }
938      }
939      #[cfg(feature = "cgi")]
940      "cgi" => {
941        if used_properties.contains("cgiScriptExtensions") {
942          if let Some(cgi_script_extensions) = config["cgiScriptExtensions"].as_vec() {
943            let cgi_script_extensions_iter = cgi_script_extensions.iter();
944            for cgi_script_extension_yaml in cgi_script_extensions_iter {
945              if cgi_script_extension_yaml.as_str().is_none() {
946                Err(anyhow::anyhow!("Invalid CGI script extension"))?
947              }
948            }
949          } else {
950            Err(anyhow::anyhow!(
951              "Invalid CGI script extension configuration"
952            ))?
953          }
954        }
955
956        if used_properties.contains("cgiScriptInterpreters") {
957          if let Some(cgi_script_interpreters) = config["cgiScriptInterpreters"].as_hash() {
958            for (cgi_script_interpreter_extension_unknown, cgi_script_interpreter_params_unknown) in
959              cgi_script_interpreters.iter()
960            {
961              if cgi_script_interpreter_extension_unknown.as_str().is_some() {
962                if !cgi_script_interpreter_params_unknown.is_null() {
963                  if let Some(cgi_script_interpreter_params) =
964                    cgi_script_interpreter_params_unknown.as_vec()
965                  {
966                    let cgi_script_interpreter_params_iter = cgi_script_interpreter_params.iter();
967                    for cgi_script_interpreter_param_yaml in cgi_script_interpreter_params_iter {
968                      if cgi_script_interpreter_param_yaml.as_str().is_none() {
969                        Err(anyhow::anyhow!("Invalid CGI script interpreter parameter"))?
970                      }
971                    }
972                  } else {
973                    Err(anyhow::anyhow!("Invalid CGI script interpreter parameters"))?
974                  }
975                }
976              } else {
977                Err(anyhow::anyhow!("Invalid CGI script interpreter extension"))?
978              }
979            }
980          } else {
981            Err(anyhow::anyhow!(
982              "Invalid CGI script interpreter configuration"
983            ))?
984          }
985        }
986      }
987      #[cfg(feature = "scgi")]
988      "scgi" => {
989        if used_properties.contains("scgiTo") && config["scgiTo"].as_str().is_none() {
990          Err(anyhow::anyhow!("Invalid SCGI target URL value"))?
991        }
992
993        if used_properties.contains("scgiPath") && config["scgiPath"].as_str().is_none() {
994          Err(anyhow::anyhow!("Invalid SCGI path"))?
995        }
996      }
997      #[cfg(feature = "fcgi")]
998      "fcgi" => {
999        if used_properties.contains("fcgiScriptExtensions") {
1000          if let Some(fastcgi_script_extensions) = config["fcgiScriptExtensions"].as_vec() {
1001            let fastcgi_script_extensions_iter = fastcgi_script_extensions.iter();
1002            for fastcgi_script_extension_yaml in fastcgi_script_extensions_iter {
1003              if fastcgi_script_extension_yaml.as_str().is_none() {
1004                Err(anyhow::anyhow!("Invalid CGI script extension"))?
1005              }
1006            }
1007          } else {
1008            Err(anyhow::anyhow!(
1009              "Invalid CGI script extension configuration"
1010            ))?
1011          }
1012        }
1013
1014        if used_properties.contains("fcgiTo") && config["fcgiTo"].as_str().is_none() {
1015          Err(anyhow::anyhow!("Invalid FastCGI target URL value"))?
1016        }
1017
1018        if used_properties.contains("fcgiPath") && config["fcgiPath"].as_str().is_none() {
1019          Err(anyhow::anyhow!("Invalid FastCGI path"))?
1020        }
1021      }
1022      #[cfg(feature = "fauth")]
1023      "fauth" => {
1024        if used_properties.contains("authTo") && config["authTo"].as_str().is_none() {
1025          Err(anyhow::anyhow!(
1026            "Invalid forwarded authentication target URL value"
1027          ))?
1028        }
1029
1030        if used_properties.contains("forwardedAuthCopyHeaders") {
1031          if let Some(modules) = config["forwardedAuthCopyHeaders"].as_vec() {
1032            let modules_iter = modules.iter();
1033            for module_name_yaml in modules_iter {
1034              if module_name_yaml.as_str().is_none() {
1035                Err(anyhow::anyhow!(
1036                  "Invalid forwarded authentication response header to copy"
1037                ))?
1038              }
1039            }
1040          } else {
1041            Err(anyhow::anyhow!(
1042              "Invalid forwarded authentication response headers to copy configuration"
1043            ))?
1044          }
1045        }
1046      }
1047      #[cfg(feature = "wsgi")]
1048      "wsgi" => {
1049        if used_properties.contains("wsgiApplicationPath")
1050          && config["wsgiApplicationPath"].as_str().is_none()
1051        {
1052          Err(anyhow::anyhow!("Invalid path to the WSGI application"))?
1053        }
1054
1055        if used_properties.contains("wsgiPath") && config["wsgiPath"].as_str().is_none() {
1056          Err(anyhow::anyhow!("Invalid WSGI request base path"))?
1057        }
1058
1059        if used_properties.contains("wsgiClearModuleImportPath") {
1060          if !is_global {
1061            Err(anyhow::anyhow!(
1062              "WSGI Python module import path clearing option is not allowed in host configuration"
1063            ))?
1064          }
1065          if config["wsgiClearModuleImportPath"].as_bool().is_none() {
1066            Err(anyhow::anyhow!(
1067              "Invalid WSGI Python module import path clearing option value"
1068            ))?
1069          }
1070        }
1071      }
1072      #[cfg(feature = "wsgid")]
1073      "wsgid" => {
1074        if used_properties.contains("wsgidApplicationPath")
1075          && config["wsgidApplicationPath"].as_str().is_none()
1076        {
1077          Err(anyhow::anyhow!(
1078            "Invalid path to the WSGI (with pre-forked process pool) application"
1079          ))?
1080        }
1081
1082        if used_properties.contains("wsgidPath") && config["wsgidPath"].as_str().is_none() {
1083          Err(anyhow::anyhow!(
1084            "Invalid WSGI (with pre-forked process pool) request base path"
1085          ))?
1086        }
1087      }
1088      #[cfg(feature = "asgi")]
1089      "asgi" => {
1090        if used_properties.contains("asgiApplicationPath")
1091          && config["asgiApplicationPath"].as_str().is_none()
1092        {
1093          Err(anyhow::anyhow!("Invalid path to the ASGI application"))?
1094        }
1095
1096        if used_properties.contains("asgiPath") && config["asgiPath"].as_str().is_none() {
1097          Err(anyhow::anyhow!("Invalid ASGI request base path"))?
1098        }
1099
1100        if used_properties.contains("asgiClearModuleImportPath") {
1101          if !is_global {
1102            Err(anyhow::anyhow!(
1103              "ASGI Python module import path clearing option is not allowed in host configuration"
1104            ))?
1105          }
1106          if config["asgiClearModuleImportPath"].as_bool().is_none() {
1107            Err(anyhow::anyhow!(
1108              "Invalid ASGI Python module import path clearing option value"
1109            ))?
1110          }
1111        }
1112      }
1113      _ => (),
1114    }
1115  }
1116
1117  Ok(used_properties.unused())
1118}
1119
1120pub fn prepare_config_for_validation(
1121  config: &Yaml,
1122) -> Result<impl Iterator<Item = (Yaml, bool, bool)>, Box<dyn Error + Send + Sync>> {
1123  let mut vector = Vec::new();
1124  if let Some(global_config) = config["global"].as_hash() {
1125    let global_config_yaml = Yaml::Hash(global_config.clone());
1126    vector.push(global_config_yaml);
1127  }
1128
1129  let mut vector2 = Vec::new();
1130  let mut vector3 = Vec::new();
1131  if !config["hosts"].is_badvalue() {
1132    if let Some(hosts) = config["hosts"].as_vec() {
1133      for host in hosts.iter() {
1134        if !host["locations"].is_badvalue() {
1135          if let Some(locations) = host["locations"].as_vec() {
1136            vector3.append(&mut locations.clone());
1137          } else {
1138            return Err(anyhow::anyhow!("Invalid location configuration").into());
1139          }
1140        }
1141      }
1142      vector2 = hosts.clone();
1143    } else {
1144      return Err(anyhow::anyhow!("Invalid virtual host configuration").into());
1145    }
1146  }
1147
1148  let iter = vector
1149    .into_iter()
1150    .map(|item| (item, true, false))
1151    .chain(vector2.into_iter().map(|item| (item, false, false)))
1152    .chain(vector3.into_iter().map(|item| (item, false, true)));
1153
1154  Ok(iter)
1155}