ferron_common/
config.rs

1use std::fmt::{Debug, Formatter};
2use std::hash::Hasher;
3use std::net::IpAddr;
4use std::sync::Arc;
5use std::{cmp::Ordering, collections::HashMap};
6
7use fancy_regex::Regex;
8
9use crate::modules::Module;
10use crate::util::IpBlockList;
11
12/// Conditional data
13#[non_exhaustive]
14#[derive(Clone, Debug)]
15pub enum ConditionalData {
16  IsRemoteIp(IpBlockList),
17  IsForwardedFor(IpBlockList),
18  IsNotRemoteIp(IpBlockList),
19  IsNotForwardedFor(IpBlockList),
20  IsEqual(String, String),
21  IsNotEqual(String, String),
22  IsRegex(String, Regex),
23  IsNotRegex(String, Regex),
24  IsRego(Arc<regorus::Engine>),
25}
26
27impl PartialEq for ConditionalData {
28  fn eq(&self, other: &Self) -> bool {
29    match (self, other) {
30      (Self::IsRemoteIp(v1), Self::IsRemoteIp(v2)) => v1 == v2,
31      (Self::IsForwardedFor(v1), Self::IsForwardedFor(v2)) => v1 == v2,
32      (Self::IsNotRemoteIp(v1), Self::IsNotRemoteIp(v2)) => v1 == v2,
33      (Self::IsNotForwardedFor(v1), Self::IsNotForwardedFor(v2)) => v1 == v2,
34      (Self::IsEqual(v1, v2), Self::IsEqual(v3, v4)) => v1 == v3 && v2 == v4,
35      (Self::IsNotEqual(v1, v2), Self::IsNotEqual(v3, v4)) => v1 == v3 && v2 == v4,
36      (Self::IsRegex(v1, v2), Self::IsRegex(v3, v4)) => v1 == v3 && v2.as_str() == v4.as_str(),
37      (Self::IsNotRegex(v1, v2), Self::IsNotRegex(v3, v4)) => v1 == v3 && v2.as_str() == v4.as_str(),
38      (Self::IsRego(v1), Self::IsRego(v2)) => v1.get_policies().ok() == v2.get_policies().ok(),
39      _ => false,
40    }
41  }
42}
43
44impl Eq for ConditionalData {}
45
46fn count_logical_slashes(s: &str) -> usize {
47  if s.is_empty() {
48    // Input is empty, zero slashes
49    return 0;
50  }
51  let trimmed = s.trim_end_matches('/');
52  if trimmed.is_empty() {
53    // Trimmed input is empty, but the original wasn't, probably input with only slashes
54    return 1;
55  }
56
57  let mut count = 0;
58  let mut prev_was_slash = false;
59
60  for ch in trimmed.chars() {
61    if ch == '/' {
62      if !prev_was_slash {
63        count += 1;
64        prev_was_slash = true;
65      }
66    } else {
67      prev_was_slash = false;
68    }
69  }
70
71  count
72}
73
74/// The struct containing conditions
75#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct Conditions {
77  /// The location prefix
78  pub location_prefix: String,
79
80  /// The conditionals
81  pub conditionals: Vec<Conditional>,
82}
83
84impl Ord for Conditions {
85  fn cmp(&self, other: &Self) -> Ordering {
86    count_logical_slashes(&self.location_prefix)
87      .cmp(&count_logical_slashes(&other.location_prefix))
88      .then_with(|| self.conditionals.len().cmp(&other.conditionals.len()))
89  }
90}
91
92impl PartialOrd for Conditions {
93  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94    Some(self.cmp(other))
95  }
96}
97
98/// The enum containing a conditional
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub enum Conditional {
101  /// "if" condition
102  If(Vec<ConditionalData>),
103
104  /// "if_not" condition
105  IfNot(Vec<ConditionalData>),
106}
107
108/// A specific Ferron server configuration
109#[derive(Clone)]
110pub struct ServerConfiguration {
111  /// Entries for the configuration
112  pub entries: HashMap<String, ServerConfigurationEntries>,
113
114  /// Configuration filters
115  pub filters: ServerConfigurationFilters,
116
117  /// Loaded modules
118  pub modules: Vec<Arc<dyn Module + Send + Sync>>,
119}
120
121impl Debug for ServerConfiguration {
122  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
123    f.debug_struct("ServerConfiguration")
124      .field("entries", &self.entries)
125      .field("filters", &self.filters)
126      .finish()
127  }
128}
129
130/// A error handler status code
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum ErrorHandlerStatus {
133  /// Any status code
134  Any,
135
136  /// Specific status code
137  Status(u16),
138}
139
140/// A Ferron server configuration filter
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct ServerConfigurationFilters {
143  /// Whether the configuration represents a host block
144  pub is_host: bool,
145
146  /// The hostname
147  pub hostname: Option<String>,
148
149  /// The IP address
150  pub ip: Option<IpAddr>,
151
152  /// The port
153  pub port: Option<u16>,
154
155  /// The conditions
156  pub condition: Option<Conditions>,
157
158  /// The error handler status code
159  pub error_handler_status: Option<ErrorHandlerStatus>,
160}
161
162impl ServerConfigurationFilters {
163  /// Checks if the server configuration is global
164  pub fn is_global(&self) -> bool {
165    self.is_host
166      && self.hostname.is_none()
167      && self.ip.is_none()
168      && self.port.is_none()
169      && self.condition.is_none()
170      && self.error_handler_status.is_none()
171  }
172
173  /// Checks if the server configuration is global and doesn't represent a host block
174  pub fn is_global_non_host(&self) -> bool {
175    !self.is_host
176  }
177}
178
179impl Ord for ServerConfigurationFilters {
180  fn cmp(&self, other: &Self) -> Ordering {
181    self
182      .is_host
183      .cmp(&other.is_host)
184      .then_with(|| self.port.is_some().cmp(&other.port.is_some()))
185      .then_with(|| self.ip.is_some().cmp(&other.ip.is_some()))
186      .then_with(|| {
187        self
188          .hostname
189          .as_ref()
190          .map(|h| !h.starts_with("*."))
191          .cmp(&other.hostname.as_ref().map(|h| !h.starts_with("*.")))
192      }) // Take wildcard hostnames into account
193      .then_with(|| {
194        self
195          .hostname
196          .as_ref()
197          .map(|h| h.trim_end_matches('.').chars().filter(|c| *c == '.').count())
198          .cmp(
199            &other
200              .hostname
201              .as_ref()
202              .map(|h| h.trim_end_matches('.').chars().filter(|c| *c == '.').count()),
203          )
204      }) // Take also amount of dots in hostnames (domain level) into account
205      .then_with(|| self.condition.cmp(&other.condition)) // Use `cmp` method for `Ord` trait implemented for `Condition`
206      .then_with(|| {
207        self
208          .error_handler_status
209          .is_some()
210          .cmp(&other.error_handler_status.is_some())
211      })
212  }
213}
214
215impl PartialOrd for ServerConfigurationFilters {
216  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
217    Some(self.cmp(other))
218  }
219}
220
221/// A specific list of Ferron server configuration entries
222#[derive(Debug, Clone, PartialEq, Eq, Hash)]
223pub struct ServerConfigurationEntries {
224  /// Vector of configuration entries
225  pub inner: Vec<ServerConfigurationEntry>,
226}
227
228impl ServerConfigurationEntries {
229  /// Extracts one value from the entry list
230  pub fn get_value(&self) -> Option<&ServerConfigurationValue> {
231    self.inner.last().and_then(|last_vector| last_vector.values.first())
232  }
233
234  /// Extracts one entry from the entry list
235  pub fn get_entry(&self) -> Option<&ServerConfigurationEntry> {
236    self.inner.last()
237  }
238
239  /// Extracts a vector of values from the entry list
240  pub fn get_values(&self) -> Vec<&ServerConfigurationValue> {
241    let mut iterator: Box<dyn Iterator<Item = &ServerConfigurationValue>> = Box::new(vec![].into_iter());
242    for entry in &self.inner {
243      iterator = Box::new(iterator.chain(entry.values.iter()));
244    }
245    iterator.collect()
246  }
247}
248
249/// A specific Ferron server configuration entry
250#[derive(Debug, Clone, PartialEq, Eq)]
251pub struct ServerConfigurationEntry {
252  /// Values for the entry
253  pub values: Vec<ServerConfigurationValue>,
254
255  /// Props for the entry
256  pub props: HashMap<String, ServerConfigurationValue>,
257}
258
259impl std::hash::Hash for ServerConfigurationEntry {
260  fn hash<H: Hasher>(&self, state: &mut H) {
261    // Hash the values vector
262    self.values.hash(state);
263
264    // For HashMap, we need to hash in a deterministic order
265    // since HashMap iteration order is not guaranteed
266    let mut props_vec: Vec<_> = self.props.iter().collect();
267    props_vec.sort_by(|a, b| a.0.cmp(b.0)); // Sort by key
268
269    // Hash the length first, then each key-value pair
270    props_vec.len().hash(state);
271    for (key, value) in props_vec {
272      key.hash(state);
273      value.hash(state);
274    }
275  }
276}
277
278/// A specific Ferron server configuration value
279#[derive(Debug, Clone, PartialOrd)]
280pub enum ServerConfigurationValue {
281  /// A string
282  String(String),
283
284  /// A non-float number
285  Integer(i128),
286
287  /// A floating point number
288  Float(f64),
289
290  /// A boolean
291  Bool(bool),
292
293  /// The null value
294  Null,
295}
296
297impl std::hash::Hash for ServerConfigurationValue {
298  fn hash<H: Hasher>(&self, state: &mut H) {
299    match self {
300      Self::String(s) => {
301        0u8.hash(state);
302        s.hash(state);
303      }
304      Self::Integer(i) => {
305        1u8.hash(state);
306        i.hash(state);
307      }
308      Self::Float(f) => {
309        2u8.hash(state);
310        // Convert to bits for consistent hashing
311        // Handle NaN by using a consistent bit pattern
312        if f.is_nan() {
313          f64::NAN.to_bits().hash(state);
314        } else {
315          f.to_bits().hash(state);
316        }
317      }
318      Self::Bool(b) => {
319        3u8.hash(state);
320        b.hash(state);
321      }
322      Self::Null => {
323        4u8.hash(state);
324      }
325    }
326  }
327}
328
329impl ServerConfigurationValue {
330  /// Checks if the value is a string
331  pub fn is_string(&self) -> bool {
332    matches!(self, Self::String(..))
333  }
334
335  /// Checks if the value is a non-float number
336  pub fn is_integer(&self) -> bool {
337    matches!(self, Self::Integer(..))
338  }
339
340  /// Checks if the value is a floating point number
341  #[allow(dead_code)]
342  pub fn is_float(&self) -> bool {
343    matches!(self, Self::Float(..))
344  }
345
346  /// Checks if the value is a boolean
347  pub fn is_bool(&self) -> bool {
348    matches!(self, Self::Bool(..))
349  }
350
351  /// Checks if the value is a null value
352  pub fn is_null(&self) -> bool {
353    matches!(self, Self::Null)
354  }
355
356  /// Extracts a `&str` from the value
357  pub fn as_str(&self) -> Option<&str> {
358    use ServerConfigurationValue::*;
359    match self {
360      String(s) => Some(s),
361      _ => None,
362    }
363  }
364
365  /// Extracts a `i128` from the value
366  pub fn as_i128(&self) -> Option<i128> {
367    use ServerConfigurationValue::*;
368    match self {
369      Integer(i) => Some(*i),
370      _ => None,
371    }
372  }
373
374  /// Extracts a `f64` from the value
375  #[allow(dead_code)]
376  pub fn as_f64(&self) -> Option<f64> {
377    match self {
378      Self::Float(i) => Some(*i),
379      _ => None,
380    }
381  }
382
383  /// Extracts a `bool` from the value
384  pub fn as_bool(&self) -> Option<bool> {
385    if let Self::Bool(v) = self {
386      Some(*v)
387    } else {
388      None
389    }
390  }
391}
392
393impl Eq for ServerConfigurationValue {}
394
395impl PartialEq for ServerConfigurationValue {
396  fn eq(&self, other: &Self) -> bool {
397    match (self, other) {
398      (Self::Bool(left), Self::Bool(right)) => left == right,
399      (Self::Integer(left), Self::Integer(right)) => left == right,
400      (Self::Float(left), Self::Float(right)) => {
401        let left = if left == &f64::NEG_INFINITY {
402          -f64::MAX
403        } else if left == &f64::INFINITY {
404          f64::MAX
405        } else if left.is_nan() {
406          0.0
407        } else {
408          *left
409        };
410
411        let right = if right == &f64::NEG_INFINITY {
412          -f64::MAX
413        } else if right == &f64::INFINITY {
414          f64::MAX
415        } else if right.is_nan() {
416          0.0
417        } else {
418          *right
419        };
420
421        left == right
422      }
423      (Self::String(left), Self::String(right)) => left == right,
424      _ => core::mem::discriminant(self) == core::mem::discriminant(other),
425    }
426  }
427}