1use std::{
2 collections::{HashMap, HashSet},
3 error::Error,
4 fs,
5 net::{IpAddr, SocketAddr},
6 path::{Path, PathBuf},
7 str::FromStr,
8};
9
10use glob::glob;
11use kdl::{KdlDocument, KdlNode, KdlValue};
12
13use crate::config::{
14 parse_conditional_data, Conditional, ConditionalData, Conditions, ErrorHandlerStatus, ServerConfiguration,
15 ServerConfigurationEntries, ServerConfigurationEntry, ServerConfigurationFilters, ServerConfigurationValue,
16};
17
18use super::ConfigurationAdapter;
19
20fn kdl_node_to_configuration_entry(kdl_node: &KdlNode) -> ServerConfigurationEntry {
21 let mut values = Vec::new();
22 let mut props = HashMap::new();
23 for kdl_entry in kdl_node.iter() {
24 let value = match kdl_entry.value().to_owned() {
25 KdlValue::String(value) => ServerConfigurationValue::String(value),
26 KdlValue::Integer(value) => ServerConfigurationValue::Integer(value),
27 KdlValue::Float(value) => ServerConfigurationValue::Float(value),
28 KdlValue::Bool(value) => ServerConfigurationValue::Bool(value),
29 KdlValue::Null => ServerConfigurationValue::Null,
30 };
31 if let Some(prop_name) = kdl_entry.name() {
32 props.insert(prop_name.value().to_string(), value);
33 } else {
34 values.push(value);
35 }
36 }
37 if values.is_empty() {
38 values.push(ServerConfigurationValue::Bool(true));
40 }
41 ServerConfigurationEntry { values, props }
42}
43
44fn load_configuration_inner(
45 path: PathBuf,
46 loaded_paths: &mut HashSet<PathBuf>,
47) -> Result<Vec<ServerConfiguration>, Box<dyn Error + Send + Sync>> {
48 let canonical_pathbuf = fs::canonicalize(&path).unwrap_or_else(|_| path.clone());
50
51 if loaded_paths.contains(&canonical_pathbuf) {
53 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
54
55 Err(anyhow::anyhow!(
56 "Detected the server configuration file include loop while attempting to load \"{}\"",
57 canonical_path
58 ))?
59 } else {
60 loaded_paths.insert(canonical_pathbuf.clone());
61 }
62
63 let file_contents = match fs::read_to_string(&path) {
65 Ok(file) => file,
66 Err(err) => {
67 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
68
69 Err(anyhow::anyhow!(
70 "Failed to read from the server configuration file at \"{}\": {}",
71 canonical_path,
72 err
73 ))?
74 }
75 };
76
77 let kdl_document: KdlDocument = match file_contents.parse() {
79 Ok(document) => document,
80 Err(err) => {
81 let err: miette::Error = err.into();
82 Err(anyhow::anyhow!(
83 "Failed to parse the server configuration file: {:?}",
84 err
85 ))?
86 }
87 };
88
89 let mut configurations = Vec::new();
91
92 let mut loaded_conditions: HashMap<String, Vec<ConditionalData>> = HashMap::new();
94
95 let mut snippets: HashMap<String, KdlDocument> = HashMap::new();
97
98 for kdl_node in kdl_document {
100 let global_name = kdl_node.name().value();
101 let children = kdl_node.children();
102 if global_name == "snippet" {
103 if let Some(snippet_name) = kdl_node.get(0).and_then(|v| v.as_string()) {
104 if let Some(children) = children {
105 snippets.insert(snippet_name.to_string(), children.to_owned());
106 } else {
107 Err(anyhow::anyhow!("Snippet \"{snippet_name}\" is missing children"))?
108 }
109 } else {
110 Err(anyhow::anyhow!("Invalid or missing snippet name"))?
111 }
112 } else if let Some(children) = children {
113 for global_name in global_name.split(",") {
114 let host_filter = if global_name == "globals" {
115 (None, None, None, false)
116 } else if let Ok(socket_addr) = global_name.parse::<SocketAddr>() {
117 (None, Some(socket_addr.ip()), Some(socket_addr.port()), true)
118 } else if let Some((address, port_str)) = global_name.rsplit_once(':') {
119 if let Ok(port) = port_str.parse::<u16>() {
120 if let Ok(ip_address) = address
121 .strip_prefix('[')
122 .and_then(|s| s.strip_suffix(']'))
123 .unwrap_or(address)
124 .parse::<IpAddr>()
125 {
126 (None, Some(ip_address), Some(port), true)
127 } else if address == "*" || address.is_empty() {
128 (None, None, Some(port), true)
129 } else {
130 (Some(address.to_string()), None, Some(port), true)
131 }
132 } else if port_str == "*" {
133 if let Ok(ip_address) = address
134 .strip_prefix('[')
135 .and_then(|s| s.strip_suffix(']'))
136 .unwrap_or(address)
137 .parse::<IpAddr>()
138 {
139 (None, Some(ip_address), None, true)
140 } else if address == "*" || address.is_empty() {
141 (None, None, None, true)
142 } else {
143 (Some(address.to_string()), None, None, true)
144 }
145 } else {
146 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
147
148 Err(anyhow::anyhow!("Invalid host specifier at \"{}\"", canonical_path))?
149 }
150 } else if let Ok(ip_address) = global_name
151 .strip_prefix('[')
152 .and_then(|s| s.strip_suffix(']'))
153 .unwrap_or(global_name)
154 .parse::<IpAddr>()
155 {
156 (None, Some(ip_address), None, true)
157 } else if global_name == "*" || global_name.is_empty() {
158 (None, None, None, true)
159 } else {
160 (Some(global_name.to_string()), None, None, true)
161 };
162
163 let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
164 for kdl_node in children.nodes() {
165 #[allow(clippy::too_many_arguments)]
166 fn kdl_iterate_fn(
167 canonical_pathbuf: &PathBuf,
168 host_filter: &(Option<String>, Option<IpAddr>, Option<u16>, bool),
169 configurations: &mut Vec<ServerConfiguration>,
170 configuration_entries: &mut HashMap<String, ServerConfigurationEntries>,
171 kdl_node: &KdlNode,
172 conditions: &mut Option<&mut Conditions>,
173 is_error_config: bool,
174 loaded_conditions: &mut HashMap<String, Vec<ConditionalData>>,
175 snippets: &HashMap<String, KdlDocument>,
176 ) -> Result<(), Box<dyn Error + Send + Sync>> {
177 let (hostname, ip, port, is_host) = host_filter;
178 let kdl_node_name = kdl_node.name().value();
179 let children = kdl_node.children();
180 if kdl_node_name == "use" {
181 if let Some(snippet_name) = kdl_node.entry(0).and_then(|e| e.value().as_string()) {
182 if let Some(snippet) = snippets.get(snippet_name) {
183 for kdl_node in snippet.nodes() {
184 kdl_iterate_fn(
185 canonical_pathbuf,
186 host_filter,
187 configurations,
188 configuration_entries,
189 kdl_node,
190 conditions,
191 is_error_config,
192 loaded_conditions,
193 snippets,
194 )?;
195 }
196 } else {
197 Err(anyhow::anyhow!(
198 "Snippet not defined: {snippet_name}. You might need to define it before using it"
199 ))?;
200 }
201 } else {
202 Err(anyhow::anyhow!("Invalid `use` statement"))?;
203 }
204 } else if kdl_node_name == "location" {
205 if is_error_config {
206 Err(anyhow::anyhow!("Locations in error configurations aren't allowed"))?;
207 } else if conditions.is_some() {
208 Err(anyhow::anyhow!(
209 "Nested locations and locations in conditions aren't allowed"
210 ))?;
211 }
212 let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
213 if let Some(children) = children {
214 if let Some(location) = kdl_node.entry(0) {
215 if let Some(location_str) = location.value().as_string() {
216 let mut conditions = Conditions {
217 location_prefix: location_str.to_string(),
218 conditionals: vec![],
219 };
220 let mut loaded_conditions = loaded_conditions.clone();
221 for kdl_node in children.nodes() {
222 kdl_iterate_fn(
223 canonical_pathbuf,
224 host_filter,
225 configurations,
226 &mut configuration_entries,
227 kdl_node,
228 &mut Some(&mut conditions),
229 is_error_config,
230 &mut loaded_conditions,
231 snippets,
232 )?;
233 }
234 if kdl_node
235 .entry("remove_base")
236 .and_then(|e| e.value().as_bool())
237 .unwrap_or(false)
238 {
239 configuration_entries.insert(
240 "UNDOCUMENTED_REMOVE_PATH_PREFIX".to_string(),
241 ServerConfigurationEntries {
242 inner: vec![ServerConfigurationEntry {
243 values: vec![ServerConfigurationValue::String(location_str.to_string())],
244 props: HashMap::new(),
245 }],
246 },
247 );
248 }
249 configurations.push(ServerConfiguration {
250 entries: configuration_entries,
251 filters: ServerConfigurationFilters {
252 is_host: *is_host,
253 hostname: hostname.clone(),
254 ip: *ip,
255 port: *port,
256 condition: Some(conditions),
257 error_handler_status: None,
258 },
259 modules: vec![],
260 });
261 } else {
262 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
263
264 Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
265 }
266 } else {
267 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
268
269 Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
270 }
271 } else {
272 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
273
274 Err(anyhow::anyhow!(
275 "Locations should have children, but they don't at \"{}\"",
276 canonical_path
277 ))?
278 }
279 } else if kdl_node_name == "condition" {
280 if is_error_config {
281 Err(anyhow::anyhow!("Conditions in error configurations aren't allowed"))?;
282 }
283 if let Some(children) = children {
284 if let Some(condition_name) = kdl_node.entry(0) {
285 if let Some(condition_name_str) = condition_name.value().as_string() {
286 let mut conditions_data = Vec::new();
287
288 for kdl_node in children.nodes() {
289 let value = kdl_node_to_configuration_entry(kdl_node);
290 let name = kdl_node.name().value();
291 conditions_data.push(match parse_conditional_data(name, value) {
292 Ok(d) => d,
293 Err(err) => Err(anyhow::anyhow!(
294 "Invalid or unsupported subcondition at \"{condition_name_str}\" condition: {err}"
295 ))?,
296 });
297 }
298
299 loaded_conditions.insert(condition_name_str.to_string(), conditions_data);
300 } else {
301 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
302
303 Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
304 }
305 } else {
306 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
307
308 Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
309 }
310 } else {
311 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
312
313 Err(anyhow::anyhow!(
314 "Locations should have children, but they don't at \"{}\"",
315 canonical_path
316 ))?
317 }
318 } else if kdl_node_name == "if" {
319 if is_error_config {
320 Err(anyhow::anyhow!("Conditions in error configurations aren't allowed"))?;
321 }
322 let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
323 if let Some(children) = children {
324 if let Some(condition_name) = kdl_node.entry(0) {
325 if let Some(condition_name_str) = condition_name.value().as_string() {
326 let mut new_conditions = if let Some(conditions) = conditions {
327 conditions.clone()
328 } else {
329 Conditions {
330 location_prefix: "/".to_string(),
331 conditionals: vec![],
332 }
333 };
334
335 if let Some(conditionals) = loaded_conditions.get(condition_name_str) {
336 new_conditions.conditionals.push(Conditional::If(conditionals.clone()));
337 } else {
338 Err(anyhow::anyhow!(
339 "Condition not defined: {condition_name_str}. You might need to define it before using it"
340 ))?;
341 }
342
343 let mut loaded_conditions = loaded_conditions.clone();
344 for kdl_node in children.nodes() {
345 kdl_iterate_fn(
346 canonical_pathbuf,
347 host_filter,
348 configurations,
349 &mut configuration_entries,
350 kdl_node,
351 &mut Some(&mut new_conditions),
352 is_error_config,
353 &mut loaded_conditions,
354 snippets,
355 )?;
356 }
357
358 configurations.push(ServerConfiguration {
359 entries: configuration_entries,
360 filters: ServerConfigurationFilters {
361 is_host: *is_host,
362 hostname: hostname.clone(),
363 ip: *ip,
364 port: *port,
365 condition: Some(new_conditions.to_owned()),
366 error_handler_status: None,
367 },
368 modules: vec![],
369 });
370 } else {
371 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
372
373 Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
374 }
375 } else {
376 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
377
378 Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
379 }
380 } else {
381 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
382
383 Err(anyhow::anyhow!(
384 "Locations should have children, but they don't at \"{}\"",
385 canonical_path
386 ))?
387 }
388 } else if kdl_node_name == "if_not" {
389 if is_error_config {
390 Err(anyhow::anyhow!("Conditions in error configurations aren't allowed"))?;
391 }
392 let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
393 if let Some(children) = children {
394 if let Some(condition_name) = kdl_node.entry(0) {
395 if let Some(condition_name_str) = condition_name.value().as_string() {
396 let mut new_conditions = if let Some(conditions) = conditions {
397 conditions.clone()
398 } else {
399 Conditions {
400 location_prefix: "/".to_string(),
401 conditionals: vec![],
402 }
403 };
404
405 if let Some(conditionals) = loaded_conditions.get(condition_name_str) {
406 new_conditions
407 .conditionals
408 .push(Conditional::IfNot(conditionals.clone()));
409 } else {
410 Err(anyhow::anyhow!(
411 "Condition not defined: {condition_name_str}. You might need to define it before using it"
412 ))?;
413 }
414
415 let mut loaded_conditions = loaded_conditions.clone();
416 for kdl_node in children.nodes() {
417 kdl_iterate_fn(
418 canonical_pathbuf,
419 host_filter,
420 configurations,
421 &mut configuration_entries,
422 kdl_node,
423 &mut Some(&mut new_conditions),
424 is_error_config,
425 &mut loaded_conditions,
426 snippets,
427 )?;
428 }
429
430 configurations.push(ServerConfiguration {
431 entries: configuration_entries,
432 filters: ServerConfigurationFilters {
433 is_host: *is_host,
434 hostname: hostname.clone(),
435 ip: *ip,
436 port: *port,
437 condition: Some(new_conditions.to_owned()),
438 error_handler_status: None,
439 },
440 modules: vec![],
441 });
442 } else {
443 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
444
445 Err(anyhow::anyhow!("Invalid location path at \"{}\"", canonical_path))?
446 }
447 } else {
448 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
449
450 Err(anyhow::anyhow!("Invalid location at \"{}\"", canonical_path))?
451 }
452 } else {
453 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
454
455 Err(anyhow::anyhow!(
456 "Locations should have children, but they don't at \"{}\"",
457 canonical_path
458 ))?
459 }
460 } else if kdl_node_name == "error_config" {
461 if is_error_config {
462 Err(anyhow::anyhow!("Nested error configurations aren't allowed"))?;
463 }
464 let mut configuration_entries: HashMap<String, ServerConfigurationEntries> = HashMap::new();
465 if let Some(children) = children {
466 if let Some(error_status_code) = kdl_node.entry(0) {
467 if let Some(error_status_code) = error_status_code.value().as_integer() {
468 let mut loaded_conditions = loaded_conditions.clone();
469 for kdl_node in children.nodes() {
470 kdl_iterate_fn(
471 canonical_pathbuf,
472 host_filter,
473 configurations,
474 &mut configuration_entries,
475 kdl_node,
476 conditions,
477 true,
478 &mut loaded_conditions,
479 snippets,
480 )?;
481 }
482 configurations.push(ServerConfiguration {
483 entries: configuration_entries,
484 filters: ServerConfigurationFilters {
485 is_host: *is_host,
486 hostname: hostname.clone(),
487 ip: *ip,
488 port: *port,
489 condition: None,
490 error_handler_status: Some(ErrorHandlerStatus::Status(error_status_code as u16)),
491 },
492 modules: vec![],
493 });
494 } else {
495 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
496
497 Err(anyhow::anyhow!(
498 "Invalid error handler status code at \"{}\"",
499 canonical_path
500 ))?
501 }
502 } else {
503 for kdl_node in children.nodes() {
504 let kdl_node_name = kdl_node.name().value();
505 let value = kdl_node_to_configuration_entry(kdl_node);
506 if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
507 entries.inner.push(value);
508 } else {
509 configuration_entries.insert(
510 kdl_node_name.to_string(),
511 ServerConfigurationEntries { inner: vec![value] },
512 );
513 }
514 }
515 configurations.push(ServerConfiguration {
516 entries: configuration_entries,
517 filters: ServerConfigurationFilters {
518 is_host: *is_host,
519 hostname: hostname.clone(),
520 ip: *ip,
521 port: *port,
522 condition: None,
523 error_handler_status: Some(ErrorHandlerStatus::Any),
524 },
525 modules: vec![],
526 });
527 }
528 } else {
529 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
530
531 Err(anyhow::anyhow!(
532 "Error handler blocks should have children, but they don't at \"{}\"",
533 canonical_path
534 ))?
535 }
536 } else {
537 let value = kdl_node_to_configuration_entry(kdl_node);
538 if let Some(entries) = configuration_entries.get_mut(kdl_node_name) {
539 entries.inner.push(value);
540 } else {
541 configuration_entries.insert(
542 kdl_node_name.to_string(),
543 ServerConfigurationEntries { inner: vec![value] },
544 );
545 }
546 }
547 Ok(())
548 }
549 kdl_iterate_fn(
550 &canonical_pathbuf,
551 &host_filter,
552 &mut configurations,
553 &mut configuration_entries,
554 kdl_node,
555 &mut None,
556 false,
557 &mut loaded_conditions,
558 &snippets,
559 )?;
560 }
561 let (hostname, ip, port, is_host) = host_filter;
562 configurations.push(ServerConfiguration {
563 entries: configuration_entries,
564 filters: ServerConfigurationFilters {
565 is_host,
566 hostname,
567 ip,
568 port,
569 condition: None,
570 error_handler_status: None,
571 },
572 modules: vec![],
573 });
574 }
575 } else if global_name == "include" {
576 let mut include_files = Vec::new();
578 for include_one in kdl_node.entries() {
579 if include_one.name().is_some() {
580 continue;
581 }
582 if let Some(include_glob) = include_one.value().as_string() {
583 let include_glob_pathbuf = match PathBuf::from_str(include_glob) {
584 Ok(pathbuf) => pathbuf,
585 Err(err) => {
586 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
587
588 Err(anyhow::anyhow!(
589 "Failed to determine includes for the server configuration file at \"{}\": {}",
590 canonical_path,
591 err
592 ))?
593 }
594 };
595 let include_glob_pathbuf_canonicalized = if include_glob_pathbuf.is_absolute() {
596 include_glob_pathbuf
597 } else {
598 let mut canonical_dirname = canonical_pathbuf.clone();
599 canonical_dirname.pop();
600 canonical_dirname.join(include_glob_pathbuf)
601 };
602 let files_globbed = match glob(&include_glob_pathbuf_canonicalized.to_string_lossy()) {
603 Ok(files_globbed) => files_globbed,
604 Err(err) => {
605 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
606
607 Err(anyhow::anyhow!(
608 "Failed to determine includes for the server configuration file at \"{}\": {}",
609 canonical_path,
610 err
611 ))?
612 }
613 };
614
615 for file_globbed_result in files_globbed {
616 let file_globbed = match file_globbed_result {
617 Ok(file_globbed) => file_globbed,
618 Err(err) => {
619 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
620
621 Err(anyhow::anyhow!(
622 "Failed to determine includes for the server configuration file at \"{}\": {}",
623 canonical_path,
624 err
625 ))?
626 }
627 };
628 include_files.push(fs::canonicalize(&file_globbed).unwrap_or_else(|_| file_globbed.clone()));
629 }
630 }
631 }
632
633 for included_file in include_files {
634 configurations.extend(load_configuration_inner(included_file, loaded_paths)?);
635 }
636 } else {
637 let canonical_path = canonical_pathbuf.to_string_lossy().into_owned();
638
639 Err(anyhow::anyhow!("Invalid top-level directive at \"{}\"", canonical_path))?
640 }
641 }
642
643 Ok(configurations)
644}
645
646pub struct KdlConfigurationAdapter;
648
649impl KdlConfigurationAdapter {
650 pub fn new() -> Self {
652 Self
653 }
654}
655
656impl ConfigurationAdapter for KdlConfigurationAdapter {
657 fn load_configuration(&self, path: &Path) -> Result<Vec<ServerConfiguration>, Box<dyn Error + Send + Sync>> {
658 load_configuration_inner(path.to_path_buf(), &mut HashSet::new())
659 }
660}