ferron/config/lookup/
conditionals.rs

1use std::collections::{BTreeMap, HashSet};
2use std::error::Error;
3use std::sync::Arc;
4use std::{collections::HashMap, net::IpAddr};
5
6use ferron_common::util::{parse_q_value_header, replace_header_placeholders};
7use ferron_common::{
8  config::{Conditional, ConditionalData},
9  modules::SocketData,
10};
11
12/// Condition match data
13pub struct ConditionMatchData<'a> {
14  pub request: &'a hyper::http::request::Parts,
15  pub socket_data: &'a SocketData,
16}
17
18/// Matches conditional
19pub fn match_conditional<'a>(
20  conditional: &'a Conditional,
21  match_data: &'a ConditionMatchData<'a>,
22) -> Result<bool, Box<dyn Error + Send + Sync>> {
23  let ConditionMatchData { request, socket_data } = match_data;
24  if !(match conditional {
25    Conditional::If(data) => {
26      let mut matches = true;
27      let mut constants = HashMap::new();
28      for d in data {
29        if !match_condition(d, request, socket_data, &mut constants)? {
30          matches = false;
31          break;
32        }
33      }
34      matches
35    }
36    Conditional::IfNot(data) => {
37      let mut matches = true;
38      let mut constants = HashMap::new();
39      for d in data {
40        if !match_condition(d, request, socket_data, &mut constants)? {
41          matches = false;
42          break;
43        }
44      }
45      !matches
46    }
47  }) {
48    return Ok(false);
49  }
50  Ok(true)
51}
52
53/// Matches a condition
54fn match_condition(
55  condition: &ConditionalData,
56  request: &hyper::http::request::Parts,
57  socket_data: &SocketData,
58  constants: &mut HashMap<String, String>,
59) -> Result<bool, Box<dyn Error + Send + Sync>> {
60  match condition {
61    ConditionalData::IsRemoteIp(list) => Ok(list.is_blocked(socket_data.remote_addr.ip())),
62    ConditionalData::IsForwardedFor(list) => {
63      let client_ip =
64        if let Some(x_forwarded_for) = request.headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
65          let prepared_remote_ip_str = match x_forwarded_for.split(",").next() {
66            Some(ip_address_str) => ip_address_str.replace(" ", ""),
67            None => return Ok(false),
68          };
69
70          let prepared_remote_ip: IpAddr = match prepared_remote_ip_str.parse() {
71            Ok(ip_address) => ip_address,
72            Err(_) => return Ok(false),
73          };
74
75          prepared_remote_ip
76        } else {
77          socket_data.remote_addr.ip()
78        };
79
80      Ok(list.is_blocked(client_ip))
81    }
82    ConditionalData::IsNotRemoteIp(list) => Ok(!list.is_blocked(socket_data.remote_addr.ip())),
83    ConditionalData::IsNotForwardedFor(list) => {
84      let client_ip =
85        if let Some(x_forwarded_for) = request.headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
86          let prepared_remote_ip_str = match x_forwarded_for.split(",").next() {
87            Some(ip_address_str) => ip_address_str.replace(" ", ""),
88            None => return Ok(false),
89          };
90
91          let prepared_remote_ip: IpAddr = match prepared_remote_ip_str.parse() {
92            Ok(ip_address) => ip_address,
93            Err(_) => return Ok(false),
94          };
95
96          prepared_remote_ip
97        } else {
98          socket_data.remote_addr.ip()
99        };
100
101      Ok(!list.is_blocked(client_ip))
102    }
103    ConditionalData::IsEqual(v1, v2) => Ok(
104      replace_header_placeholders(v1, request, Some(socket_data))
105        == replace_header_placeholders(v2, request, Some(socket_data)),
106    ),
107    ConditionalData::IsNotEqual(v1, v2) => Ok(
108      replace_header_placeholders(v1, request, Some(socket_data))
109        != replace_header_placeholders(v2, request, Some(socket_data)),
110    ),
111    ConditionalData::IsRegex(v1, regex) => {
112      Ok(regex.is_match(&replace_header_placeholders(v1, request, Some(socket_data)))?)
113    }
114    ConditionalData::IsNotRegex(v1, regex) => {
115      Ok(!(regex.is_match(&replace_header_placeholders(v1, request, Some(socket_data)))?))
116    }
117    ConditionalData::IsRego(rego_engine) => {
118      let mut cloned_engine = (*rego_engine.clone()).clone();
119      let mut rego_input_object = BTreeMap::new();
120      rego_input_object.insert("method".into(), request.method.as_str().into());
121      rego_input_object.insert(
122        "protocol".into(),
123        match request.version {
124          hyper::Version::HTTP_09 => "HTTP/0.9".into(),
125          hyper::Version::HTTP_10 => "HTTP/1.0".into(),
126          hyper::Version::HTTP_11 => "HTTP/1.1".into(),
127          hyper::Version::HTTP_2 => "HTTP/2.0".into(),
128          hyper::Version::HTTP_3 => "HTTP/3.0".into(),
129          _ => "HTTP/Unknown".into(),
130        },
131      );
132      rego_input_object.insert("uri".into(), request.uri.to_string().into());
133      let mut headers_hashmap_initial: HashMap<String, Vec<regorus::Value>> =
134        HashMap::with_capacity(request.headers.keys_len());
135      for (key, value) in request.headers.iter() {
136        let key_string = key.as_str().to_lowercase();
137        if let Some(header_list) = headers_hashmap_initial.get_mut(&key_string) {
138          header_list.push(value.to_str().unwrap_or("").into());
139        } else {
140          headers_hashmap_initial.insert(key_string, vec![value.to_str().unwrap_or("").into()]);
141        }
142      }
143      let mut headers_btreemap = BTreeMap::new();
144      for (key, value) in headers_hashmap_initial.into_iter() {
145        headers_btreemap.insert(key.into(), value.into());
146      }
147      let headers_rego = regorus::Value::Object(Arc::new(headers_btreemap));
148      rego_input_object.insert("headers".into(), headers_rego);
149      let mut socket_data_btreemap = BTreeMap::new();
150      socket_data_btreemap.insert("client_ip".into(), socket_data.remote_addr.ip().to_string().into());
151      socket_data_btreemap.insert("client_port".into(), (socket_data.remote_addr.port() as u32).into());
152      socket_data_btreemap.insert("server_ip".into(), socket_data.local_addr.ip().to_string().into());
153      socket_data_btreemap.insert("server_port".into(), (socket_data.local_addr.port() as u32).into());
154      socket_data_btreemap.insert("encrypted".into(), socket_data.encrypted.into());
155      let socket_data_rego = regorus::Value::Object(Arc::new(socket_data_btreemap));
156      rego_input_object.insert("socket_data".into(), socket_data_rego);
157      let mut constants_btreemap = BTreeMap::new();
158      for (key, value) in constants.iter_mut() {
159        constants_btreemap.insert(key.to_owned().into(), value.to_owned().into());
160      }
161      let constants_rego = regorus::Value::Object(Arc::new(constants_btreemap));
162      rego_input_object.insert("constants".into(), constants_rego);
163      let rego_input = regorus::Value::Object(Arc::new(rego_input_object));
164      cloned_engine.set_input(rego_input);
165      Ok(*cloned_engine.eval_rule("data.ferron.pass".to_string())?.as_bool()?)
166    }
167    ConditionalData::SetConstant(name, value) => {
168      constants.insert(name.to_owned(), value.to_owned());
169      Ok(true)
170    }
171    ConditionalData::IsLanguage(language) => {
172      let accepted_languages = parse_q_value_header(
173        request
174          .headers
175          .get(hyper::header::ACCEPT_LANGUAGE)
176          .and_then(|v| v.to_str().ok())
177          .unwrap_or("*"),
178      );
179      let supported_languages = constants
180        .get("LANGUAGES")
181        .and_then(|v| {
182          let mut hash_set: HashSet<&str> = HashSet::new();
183          for lang in v.split(",") {
184            hash_set.insert(lang);
185            if let Some((lang2, _)) = lang.split_once('-') {
186              hash_set.insert(lang2);
187            }
188          }
189          if hash_set.is_empty() {
190            None
191          } else {
192            Some(hash_set)
193          }
194        })
195        .unwrap_or(HashSet::from_iter(vec![language.as_str()]));
196      Ok(
197        accepted_languages
198          .iter()
199          .find(|l| {
200            *l == "*"
201              || supported_languages.contains(l.as_str())
202              || l.split_once('-').is_some_and(|(v, _)| supported_languages.contains(v))
203          })
204          .is_some_and(|l| l == language || language.split_once('-').is_some_and(|(v, _)| v == l)),
205      )
206    }
207    _ => Ok(false),
208  }
209}