ferron/config/lookup/
mod.rs

1pub(super) mod conditionals;
2mod tree;
3
4use std::{collections::HashMap, net::IpAddr, sync::Arc};
5
6use ferron_common::{
7  config::{ErrorHandlerStatus, ServerConfiguration, ServerConfigurationFilters},
8  modules::SocketData,
9};
10use hashlink::LinkedHashMap;
11
12use crate::config::lookup::{
13  conditionals::ConditionMatchData,
14  tree::{ConfigFilterTree, ConfigFilterTreeSingleKey},
15};
16
17/// A type alias for the error handler status lookup structure, using Arc to allow for shared ownership of server configurations.
18pub type ErrorHandlerStatusLookupWithConfiguration = ErrorHandlerStatusLookup<Arc<ServerConfiguration>>;
19
20/// Converts the filters of a server configuration into a node key for the configuration filter tree.
21#[inline]
22fn convert_filters_to_node_key(filters: &ServerConfigurationFilters) -> Vec<ConfigFilterTreeSingleKey> {
23  let mut node_key = Vec::new();
24  if filters.is_host {
25    node_key.push(ConfigFilterTreeSingleKey::IsHostConfiguration);
26
27    if let Some(port) = filters.port {
28      node_key.push(ConfigFilterTreeSingleKey::Port(port));
29    }
30
31    if let Some(ip) = filters.ip.map(|c| c.to_canonical()) {
32      if ip.is_loopback() {
33        node_key.push(ConfigFilterTreeSingleKey::IsLocalhost);
34      } else {
35        match ip {
36          IpAddr::V4(ipv4) => {
37            for octet in ipv4.octets() {
38              node_key.push(ConfigFilterTreeSingleKey::IPv4Octet(octet));
39            }
40          }
41          IpAddr::V6(ipv6) => {
42            for octet in ipv6.octets() {
43              node_key.push(ConfigFilterTreeSingleKey::IPv6Octet(octet));
44            }
45          }
46        }
47      }
48    }
49
50    if let Some(hostname) = &filters.hostname {
51      for part in hostname.split('.').rev() {
52        if part.is_empty() {
53          continue;
54        }
55        match part {
56          "*" => node_key.push(ConfigFilterTreeSingleKey::HostDomainLevelWildcard),
57          _ => node_key.push(ConfigFilterTreeSingleKey::HostDomainLevel(part.to_string())),
58        }
59      }
60    }
61
62    if let Some(conditions) = &filters.condition {
63      let mut is_first = true;
64      for segment in conditions.location_prefix.split("/") {
65        if is_first || !segment.is_empty() {
66          node_key.push(ConfigFilterTreeSingleKey::LocationSegment(segment.to_string()));
67        }
68        is_first = false;
69      }
70      for conditional in &conditions.conditionals {
71        // Had to clone the conditional here because the ConfigFilterTreeSingleKey::Conditional variant needs
72        // to own its data, and we can't move it out of the loop since it's borrowed from the filters struct...
73        node_key.push(ConfigFilterTreeSingleKey::Conditional(conditional.clone()));
74      }
75    }
76  }
77
78  node_key
79}
80
81/// A lookup structure for error handler status codes, allowing for specific status codes,
82/// a catch-all for any status code, and a default value if no other matches are found.
83#[derive(Debug)]
84pub struct ErrorHandlerStatusLookup<T> {
85  default_value: Option<T>,
86  catchall_value: Option<T>,
87  status_code_values: HashMap<u16, T>,
88}
89
90impl<T> ErrorHandlerStatusLookup<T> {
91  fn new() -> Self {
92    Self {
93      default_value: None,
94      catchall_value: None,
95      status_code_values: HashMap::new(),
96    }
97  }
98
99  pub fn get(&self, status_code: u16) -> Option<&T> {
100    self
101      .status_code_values
102      .get(&status_code)
103      .or(self.catchall_value.as_ref())
104  }
105
106  pub fn get_default(&self) -> Option<&T> {
107    self.default_value.as_ref()
108  }
109
110  fn insert(&mut self, status_code: Option<u16>, value: T) {
111    if let Some(code) = status_code {
112      self.status_code_values.insert(code, value);
113    } else {
114      self.catchall_value = Some(value);
115    }
116  }
117
118  fn set_default(&mut self, value: T) {
119    self.default_value = Some(value);
120  }
121
122  pub fn has_status_codes(&self) -> bool {
123    self.catchall_value.is_some() || !self.status_code_values.is_empty()
124  }
125}
126
127#[derive(Debug)]
128pub struct ServerConfigurations {
129  inner: ConfigFilterTree<ErrorHandlerStatusLookupWithConfiguration>,
130
131  /// A vector of all host configurations, used for quickly finding host configurations without needing
132  /// to traverse the configuration filter tree
133  pub host_configs: Vec<Arc<ServerConfiguration>>,
134}
135
136impl ServerConfigurations {
137  /// Creates the server configurations struct
138  pub fn new(mut inner: Vec<ServerConfiguration>) -> Self {
139    // Reverse the inner vector to ensure the location configurations are checked in the correct order
140    inner.reverse();
141
142    // Sort the configurations array by the specifity of configurations,
143    // so that it will be possible to insert configurations into the configuration filter tree in a single pass,
144    // without needing to backtrack to update parent nodes with default values from more specific child nodes
145    inner.sort_by(|a, b| a.filters.cmp(&b.filters));
146
147    let mut new_inner = ConfigFilterTree::new();
148    let mut host_config_filters = LinkedHashMap::new();
149
150    for config in inner {
151      let config = Arc::new(config);
152      if config.filters.is_host {
153        if config.filters.condition.is_none() && config.filters.error_handler_status.is_none() {
154          host_config_filters.insert(
155            (config.filters.hostname.clone(), config.filters.port, config.filters.ip),
156            config.clone(),
157          );
158        } else {
159          host_config_filters
160            .entry((config.filters.hostname.clone(), config.filters.port, config.filters.ip))
161            .or_insert_with(|| config.clone());
162        }
163      }
164      let error_handler_status = &config.filters.error_handler_status;
165      let node_key = convert_filters_to_node_key(&config.filters);
166      let parent_config_default_value = new_inner
167        .get(node_key, None)
168        .expect("configuration filter tree's get method shouldn't error out if no conditionals are checked against")
169        .and_then(|lookup: &ErrorHandlerStatusLookup<_>| lookup.get_default().cloned());
170      let node_key = convert_filters_to_node_key(&config.filters);
171      let value_option = new_inner.insert_node(node_key);
172      if value_option.is_none() {
173        let mut new_value = ErrorHandlerStatusLookup::new();
174        if let Some(parent_default) = parent_config_default_value {
175          new_value.set_default(parent_default);
176        }
177        *value_option = Some(new_value);
178      }
179      if let Some(value) = value_option.as_mut() {
180        match error_handler_status {
181          Some(ErrorHandlerStatus::Status(status_code)) => value.insert(Some(*status_code), config),
182          Some(ErrorHandlerStatus::Any) => value.insert(None, config),
183          None => value.set_default(config),
184        }
185      }
186    }
187
188    Self {
189      inner: new_inner,
190      host_configs: host_config_filters.into_iter().map(|(_, config)| config).collect(),
191    }
192  }
193
194  /// Finds a specific server configuration based on request parameters
195  pub fn find_configuration(
196    &self,
197    request: &hyper::http::request::Parts,
198    hostname: Option<&str>,
199    socket_data: &SocketData,
200  ) -> Result<Option<&ErrorHandlerStatusLookupWithConfiguration>, Box<dyn std::error::Error + Send + Sync>> {
201    let mut node_key = Vec::new();
202    node_key.push(ConfigFilterTreeSingleKey::IsHostConfiguration);
203    node_key.push(ConfigFilterTreeSingleKey::Port(socket_data.local_addr.port()));
204    let local_ip = socket_data.local_addr.ip().to_canonical();
205    if local_ip.is_loopback() {
206      node_key.push(ConfigFilterTreeSingleKey::IsLocalhost);
207    } else {
208      match local_ip {
209        IpAddr::V4(ipv4) => {
210          for octet in ipv4.octets() {
211            node_key.push(ConfigFilterTreeSingleKey::IPv4Octet(octet));
212          }
213        }
214        IpAddr::V6(ipv6) => {
215          for octet in ipv6.octets() {
216            node_key.push(ConfigFilterTreeSingleKey::IPv6Octet(octet));
217          }
218        }
219      }
220    }
221    if let Some(hostname) = hostname {
222      for part in hostname.split('.').rev() {
223        if part.is_empty() {
224          continue;
225        }
226        node_key.push(ConfigFilterTreeSingleKey::HostDomainLevel(part.to_string()))
227      }
228    }
229    for part in request.uri.path().split("/") {
230      node_key.push(ConfigFilterTreeSingleKey::LocationSegment(part.to_string()));
231    }
232
233    self
234      .inner
235      .get(node_key, Some(ConditionMatchData { request, socket_data }))
236  }
237
238  /// Finds the global server configuration (host or non-host)
239  pub fn find_global_configuration(&self) -> Option<Arc<ServerConfiguration>> {
240    self
241      .inner
242      .get(vec![ConfigFilterTreeSingleKey::IsHostConfiguration], None)
243      .expect("configuration filter tree's get method shouldn't error out if no conditionals are checked against")
244      .and_then(|lookup| lookup.get_default())
245      .cloned()
246  }
247}