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 {
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      .or(self.default_value.as_ref())
105  }
106
107  pub fn get_default(&self) -> Option<&T> {
108    self.default_value.as_ref()
109  }
110
111  fn insert(&mut self, status_code: Option<u16>, value: T) {
112    if let Some(code) = status_code {
113      self.status_code_values.insert(code, value);
114    } else {
115      self.catchall_value = Some(value);
116    }
117  }
118
119  fn set_default(&mut self, value: T) {
120    self.default_value = Some(value);
121  }
122
123  pub fn has_status_codes(&self) -> bool {
124    self.catchall_value.is_some() || !self.status_code_values.is_empty()
125  }
126}
127
128#[derive(Debug)]
129pub struct ServerConfigurations {
130  inner: ConfigFilterTree<ErrorHandlerStatusLookupWithConfiguration>,
131
132  /// A vector of all host configurations, used for quickly finding host configurations without needing
133  /// to traverse the configuration filter tree
134  pub host_configs: Vec<Arc<ServerConfiguration>>,
135}
136
137impl ServerConfigurations {
138  /// Creates the server configurations struct
139  pub fn new(mut inner: Vec<ServerConfiguration>) -> Self {
140    // Reverse the inner vector to ensure the location configurations are checked in the correct order
141    inner.reverse();
142
143    // Sort the configurations array by the specifity of configurations,
144    // so that it will be possible to insert configurations into the configuration filter tree in a single pass,
145    // without needing to backtrack to update parent nodes with default values from more specific child nodes
146    inner.sort_by(|a, b| a.filters.cmp(&b.filters));
147
148    let mut new_inner = ConfigFilterTree::new();
149    let mut host_config_filters = LinkedHashMap::new();
150
151    for config in inner {
152      let config = Arc::new(config);
153      if config.filters.is_host {
154        if config.filters.condition.is_none() && config.filters.error_handler_status.is_none() {
155          host_config_filters.insert(
156            (config.filters.hostname.clone(), config.filters.port, config.filters.ip),
157            config.clone(),
158          );
159        } else {
160          host_config_filters
161            .entry((config.filters.hostname.clone(), config.filters.port, config.filters.ip))
162            .or_insert_with(|| config.clone());
163        }
164      }
165      let error_handler_status = &config.filters.error_handler_status;
166      let node_key = convert_filters_to_node_key(&config.filters);
167      let parent_config_default_value = new_inner
168        .get(node_key, None)
169        .expect("configuration filter tree's get method shouldn't error out if no conditionals are checked against")
170        .and_then(|lookup: &ErrorHandlerStatusLookup<_>| lookup.get_default().cloned());
171      let node_key = convert_filters_to_node_key(&config.filters);
172      let value_option = new_inner.insert_node(node_key);
173      if value_option.is_none() {
174        let mut new_value = ErrorHandlerStatusLookup::new();
175        if let Some(parent_default) = parent_config_default_value {
176          new_value.set_default(parent_default);
177        }
178        *value_option = Some(new_value);
179      }
180      if let Some(value) = value_option.as_mut() {
181        match error_handler_status {
182          Some(ErrorHandlerStatus::Status(status_code)) => value.insert(Some(*status_code), config),
183          Some(ErrorHandlerStatus::Any) => value.insert(None, config),
184          None => value.set_default(config),
185        }
186      }
187    }
188
189    Self {
190      inner: new_inner,
191      host_configs: host_config_filters.into_iter().map(|(_, config)| config).collect(),
192    }
193  }
194
195  /// Finds a specific server configuration based on request parameters
196  pub fn find_configuration(
197    &self,
198    request: &hyper::http::request::Parts,
199    hostname: Option<&str>,
200    socket_data: &SocketData,
201  ) -> Result<Option<&ErrorHandlerStatusLookupWithConfiguration>, Box<dyn std::error::Error + Send + Sync>> {
202    let mut node_key = Vec::new();
203    node_key.push(ConfigFilterTreeSingleKey::IsHostConfiguration);
204    node_key.push(ConfigFilterTreeSingleKey::Port(socket_data.local_addr.port()));
205    let local_ip = socket_data.local_addr.ip();
206    if local_ip.is_loopback() {
207      node_key.push(ConfigFilterTreeSingleKey::IsLocalhost);
208    } else {
209      match local_ip {
210        IpAddr::V4(ipv4) => {
211          for octet in ipv4.octets() {
212            node_key.push(ConfigFilterTreeSingleKey::IPv4Octet(octet));
213          }
214        }
215        IpAddr::V6(ipv6) => {
216          for octet in ipv6.octets() {
217            node_key.push(ConfigFilterTreeSingleKey::IPv6Octet(octet));
218          }
219        }
220      }
221    }
222    if let Some(hostname) = hostname {
223      for part in hostname.split('.').rev() {
224        if part.is_empty() {
225          continue;
226        }
227        node_key.push(ConfigFilterTreeSingleKey::HostDomainLevel(part.to_string()))
228      }
229    }
230    for part in request.uri.path().split("/") {
231      node_key.push(ConfigFilterTreeSingleKey::LocationSegment(part.to_string()));
232    }
233
234    self
235      .inner
236      .get(node_key, Some(ConditionMatchData { request, socket_data }))
237  }
238
239  /// Finds the global server configuration (host or non-host)
240  pub fn find_global_configuration(&self) -> Option<Arc<ServerConfiguration>> {
241    self
242      .inner
243      .get(vec![ConfigFilterTreeSingleKey::IsHostConfiguration], None)
244      .expect("configuration filter tree's get method shouldn't error out if no conditionals are checked against")
245      .and_then(|lookup| lookup.get_default())
246      .cloned()
247  }
248}