ferron/util/
validate_config.rs1use 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
9struct 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
50pub 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}