1pub mod adapters;
2pub mod processing;
3
4pub use ferron_common::config::*;
5use ferron_common::util::parse_q_value_header;
6
7use std::collections::{HashMap, HashSet};
8use std::error::Error;
9use std::net::IpAddr;
10use std::sync::Arc;
11use std::{collections::BTreeMap, fmt::Debug};
12
13use fancy_regex::RegexBuilder;
14
15use crate::util::{is_localhost, match_hostname, match_location, replace_header_placeholders, IpBlockList};
16use ferron_common::modules::SocketData;
17
18pub fn parse_conditional_data(
20 name: &str,
21 value: ServerConfigurationEntry,
22) -> Result<ConditionalData, Box<dyn Error + Send + Sync>> {
23 Ok(match name {
24 "is_remote_ip" => {
25 let mut list = IpBlockList::new();
26 list.load_from_vec(value.values.iter().filter_map(|v| v.as_str()).collect());
27 ConditionalData::IsRemoteIp(list)
28 }
29 "is_forwarded_for" => {
30 let mut list = IpBlockList::new();
31 list.load_from_vec(value.values.iter().filter_map(|v| v.as_str()).collect());
32 ConditionalData::IsForwardedFor(list)
33 }
34 "is_not_remote_ip" => {
35 let mut list = IpBlockList::new();
36 list.load_from_vec(value.values.iter().filter_map(|v| v.as_str()).collect());
37 ConditionalData::IsNotRemoteIp(list)
38 }
39 "is_not_forwarded_for" => {
40 let mut list = IpBlockList::new();
41 list.load_from_vec(value.values.iter().filter_map(|v| v.as_str()).collect());
42 ConditionalData::IsNotForwardedFor(list)
43 }
44 "is_equal" => ConditionalData::IsEqual(
45 value
46 .values
47 .first()
48 .and_then(|v| v.as_str())
49 .ok_or(anyhow::anyhow!(
50 "Missing or invalid left side of a \"is_equal\" subcondition"
51 ))?
52 .to_string(),
53 value
54 .values
55 .get(1)
56 .and_then(|v| v.as_str())
57 .ok_or(anyhow::anyhow!(
58 "Missing or invalid right side of a \"is_equal\" subcondition"
59 ))?
60 .to_string(),
61 ),
62 "is_not_equal" => ConditionalData::IsNotEqual(
63 value
64 .values
65 .first()
66 .and_then(|v| v.as_str())
67 .ok_or(anyhow::anyhow!(
68 "Missing or invalid left side of a \"is_not_equal\" subcondition"
69 ))?
70 .to_string(),
71 value
72 .values
73 .get(1)
74 .and_then(|v| v.as_str())
75 .ok_or(anyhow::anyhow!(
76 "Missing or invalid right side of a \"is_not_equal\" subcondition"
77 ))?
78 .to_string(),
79 ),
80 "is_regex" => {
81 let left_side = value.values.first().and_then(|v| v.as_str()).ok_or(anyhow::anyhow!(
82 "Missing or invalid left side of a \"is_regex\" subcondition"
83 ))?;
84 let right_side = value.values.get(1).and_then(|v| v.as_str()).ok_or(anyhow::anyhow!(
85 "Missing or invalid right side of a \"is_regex\" subcondition"
86 ))?;
87 ConditionalData::IsRegex(
88 left_side.to_string(),
89 RegexBuilder::new(right_side)
90 .case_insensitive(
91 value
92 .props
93 .get("case_insensitive")
94 .and_then(|p| p.as_bool())
95 .unwrap_or(false),
96 )
97 .build()?,
98 )
99 }
100 "is_not_regex" => {
101 let left_side = value.values.first().and_then(|v| v.as_str()).ok_or(anyhow::anyhow!(
102 "Missing or invalid left side of a \"is_not_regex\" subcondition"
103 ))?;
104 let right_side = value.values.get(1).and_then(|v| v.as_str()).ok_or(anyhow::anyhow!(
105 "Missing or invalid right side of a \"is_not_regex\" subcondition"
106 ))?;
107 ConditionalData::IsNotRegex(
108 left_side.to_string(),
109 RegexBuilder::new(right_side)
110 .case_insensitive(
111 value
112 .props
113 .get("case_insensitive")
114 .and_then(|p| p.as_bool())
115 .unwrap_or(false),
116 )
117 .build()?,
118 )
119 }
120 "is_rego" => {
121 let rego_policy = value
122 .values
123 .first()
124 .and_then(|v| v.as_str())
125 .ok_or(anyhow::anyhow!("Missing or invalid Rego policy"))?;
126 let mut rego_engine = regorus::Engine::new();
127 rego_engine.add_policy("ferron.rego".to_string(), rego_policy.to_string())?;
128 ConditionalData::IsRego(Arc::new(rego_engine))
129 }
130 "set_constant" => ConditionalData::SetConstant(
131 value
132 .values
133 .first()
134 .and_then(|v| v.as_str())
135 .ok_or(anyhow::anyhow!(
136 "Missing or invalid constant name in a \"set_constant\" subcondition"
137 ))?
138 .to_string(),
139 value
140 .values
141 .get(1)
142 .and_then(|v| v.as_str())
143 .ok_or(anyhow::anyhow!(
144 "Missing or invalid constant value in a \"set_constant\" subcondition"
145 ))?
146 .to_string(),
147 ),
148 "is_language" => ConditionalData::IsLanguage(
149 value
150 .values
151 .first()
152 .and_then(|v| v.as_str())
153 .ok_or(anyhow::anyhow!(
154 "Missing or invalid desired language in a \"is_language\" subcondition"
155 ))?
156 .to_string(),
157 ),
158 _ => Err(anyhow::anyhow!("Unrecognized subcondition: {name}"))?,
159 })
160}
161
162fn match_conditions(
164 conditions: &Conditions,
165 request: &hyper::http::request::Parts,
166 socket_data: &SocketData,
167) -> Result<bool, Box<dyn Error + Send + Sync>> {
168 if !match_location(&conditions.location_prefix, request.uri.path()) {
169 return Ok(false);
170 }
171 for cond in &conditions.conditionals {
172 if !(match cond {
173 Conditional::If(data) => {
174 let mut matches = true;
175 let mut constants = HashMap::new();
176 for d in data {
177 if !match_condition(d, request, socket_data, &mut constants)? {
178 matches = false;
179 break;
180 }
181 }
182 matches
183 }
184 Conditional::IfNot(data) => {
185 let mut matches = true;
186 let mut constants = HashMap::new();
187 for d in data {
188 if !match_condition(d, request, socket_data, &mut constants)? {
189 matches = false;
190 break;
191 }
192 }
193 !matches
194 }
195 }) {
196 return Ok(false);
197 }
198 }
199 Ok(true)
200}
201
202fn match_condition(
204 condition: &ConditionalData,
205 request: &hyper::http::request::Parts,
206 socket_data: &SocketData,
207 constants: &mut HashMap<String, String>,
208) -> Result<bool, Box<dyn Error + Send + Sync>> {
209 match condition {
210 ConditionalData::IsRemoteIp(list) => Ok(list.is_blocked(socket_data.remote_addr.ip())),
211 ConditionalData::IsForwardedFor(list) => {
212 let client_ip =
213 if let Some(x_forwarded_for) = request.headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
214 let prepared_remote_ip_str = match x_forwarded_for.split(",").next() {
215 Some(ip_address_str) => ip_address_str.replace(" ", ""),
216 None => return Ok(false),
217 };
218
219 let prepared_remote_ip: IpAddr = match prepared_remote_ip_str.parse() {
220 Ok(ip_address) => ip_address,
221 Err(_) => return Ok(false),
222 };
223
224 prepared_remote_ip
225 } else {
226 socket_data.remote_addr.ip()
227 };
228
229 Ok(list.is_blocked(client_ip))
230 }
231 ConditionalData::IsNotRemoteIp(list) => Ok(!list.is_blocked(socket_data.remote_addr.ip())),
232 ConditionalData::IsNotForwardedFor(list) => {
233 let client_ip =
234 if let Some(x_forwarded_for) = request.headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
235 let prepared_remote_ip_str = match x_forwarded_for.split(",").next() {
236 Some(ip_address_str) => ip_address_str.replace(" ", ""),
237 None => return Ok(false),
238 };
239
240 let prepared_remote_ip: IpAddr = match prepared_remote_ip_str.parse() {
241 Ok(ip_address) => ip_address,
242 Err(_) => return Ok(false),
243 };
244
245 prepared_remote_ip
246 } else {
247 socket_data.remote_addr.ip()
248 };
249
250 Ok(!list.is_blocked(client_ip))
251 }
252 ConditionalData::IsEqual(v1, v2) => Ok(
253 replace_header_placeholders(v1, request, Some(socket_data))
254 == replace_header_placeholders(v2, request, Some(socket_data)),
255 ),
256 ConditionalData::IsNotEqual(v1, v2) => Ok(
257 replace_header_placeholders(v1, request, Some(socket_data))
258 != replace_header_placeholders(v2, request, Some(socket_data)),
259 ),
260 ConditionalData::IsRegex(v1, regex) => {
261 Ok(regex.is_match(&replace_header_placeholders(v1, request, Some(socket_data)))?)
262 }
263 ConditionalData::IsNotRegex(v1, regex) => {
264 Ok(!(regex.is_match(&replace_header_placeholders(v1, request, Some(socket_data)))?))
265 }
266 ConditionalData::IsRego(rego_engine) => {
267 let mut cloned_engine = (*rego_engine.clone()).clone();
268 let mut rego_input_object = BTreeMap::new();
269 rego_input_object.insert("method".into(), request.method.as_str().into());
270 rego_input_object.insert(
271 "protocol".into(),
272 match request.version {
273 hyper::Version::HTTP_09 => "HTTP/0.9".into(),
274 hyper::Version::HTTP_10 => "HTTP/1.0".into(),
275 hyper::Version::HTTP_11 => "HTTP/1.1".into(),
276 hyper::Version::HTTP_2 => "HTTP/2.0".into(),
277 hyper::Version::HTTP_3 => "HTTP/3.0".into(),
278 _ => "HTTP/Unknown".into(),
279 },
280 );
281 rego_input_object.insert("uri".into(), request.uri.to_string().into());
282 let mut headers_hashmap_initial: HashMap<String, Vec<regorus::Value>> =
283 HashMap::with_capacity(request.headers.keys_len());
284 for (key, value) in request.headers.iter() {
285 let key_string = key.as_str().to_lowercase();
286 if let Some(header_list) = headers_hashmap_initial.get_mut(&key_string) {
287 header_list.push(value.to_str().unwrap_or("").into());
288 } else {
289 headers_hashmap_initial.insert(key_string, vec![value.to_str().unwrap_or("").into()]);
290 }
291 }
292 let mut headers_btreemap = BTreeMap::new();
293 for (key, value) in headers_hashmap_initial.into_iter() {
294 headers_btreemap.insert(key.into(), value.into());
295 }
296 let headers_rego = regorus::Value::Object(Arc::new(headers_btreemap));
297 rego_input_object.insert("headers".into(), headers_rego);
298 let mut socket_data_btreemap = BTreeMap::new();
299 socket_data_btreemap.insert("client_ip".into(), socket_data.remote_addr.ip().to_string().into());
300 socket_data_btreemap.insert("client_port".into(), (socket_data.remote_addr.port() as u32).into());
301 socket_data_btreemap.insert("server_ip".into(), socket_data.local_addr.ip().to_string().into());
302 socket_data_btreemap.insert("server_port".into(), (socket_data.local_addr.port() as u32).into());
303 socket_data_btreemap.insert("encrypted".into(), socket_data.encrypted.into());
304 let socket_data_rego = regorus::Value::Object(Arc::new(socket_data_btreemap));
305 rego_input_object.insert("socket_data".into(), socket_data_rego);
306 let mut constants_btreemap = BTreeMap::new();
307 for (key, value) in constants.iter_mut() {
308 constants_btreemap.insert(key.to_owned().into(), value.to_owned().into());
309 }
310 let constants_rego = regorus::Value::Object(Arc::new(constants_btreemap));
311 rego_input_object.insert("constants".into(), constants_rego);
312 let rego_input = regorus::Value::Object(Arc::new(rego_input_object));
313 cloned_engine.set_input(rego_input);
314 Ok(*cloned_engine.eval_rule("data.ferron.pass".to_string())?.as_bool()?)
315 }
316 ConditionalData::SetConstant(name, value) => {
317 constants.insert(name.to_owned(), value.to_owned());
318 Ok(true)
319 }
320 ConditionalData::IsLanguage(language) => {
321 let accepted_languages = parse_q_value_header(
322 request
323 .headers
324 .get(hyper::header::ACCEPT_LANGUAGE)
325 .and_then(|v| v.to_str().ok())
326 .unwrap_or("*"),
327 );
328 let supported_languages = constants
329 .get("LANGUAGES")
330 .and_then(|v| {
331 let mut hash_set: HashSet<&str> = HashSet::new();
332 for lang in v.split(",") {
333 hash_set.insert(lang);
334 if let Some((lang2, _)) = lang.split_once('-') {
335 hash_set.insert(lang2);
336 }
337 }
338 if hash_set.is_empty() {
339 None
340 } else {
341 Some(hash_set)
342 }
343 })
344 .unwrap_or(HashSet::from_iter(vec![language.as_str()]));
345 Ok(
346 accepted_languages
347 .iter()
348 .find(|l| {
349 *l == "*"
350 || supported_languages.contains(l.as_str())
351 || l.split_once('-').is_some_and(|(v, _)| supported_languages.contains(v))
352 })
353 .is_some_and(|l| l == language || language.split_once('-').is_some_and(|(v, _)| v == l)),
354 )
355 }
356 _ => Ok(false),
357 }
358}
359
360#[derive(Debug)]
362pub struct ServerConfigurations {
363 pub inner: Vec<Arc<ServerConfiguration>>,
365}
366
367impl ServerConfigurations {
368 pub fn new(mut inner: Vec<ServerConfiguration>) -> Self {
370 inner.reverse();
372
373 inner.sort_by(|a, b| a.filters.cmp(&b.filters));
375 Self {
376 inner: inner.into_iter().map(Arc::new).collect(),
377 }
378 }
379
380 pub fn find_configuration(
382 &self,
383 request: &hyper::http::request::Parts,
384 hostname: Option<&str>,
385 socket_data: &SocketData,
386 ) -> Result<Option<Arc<ServerConfiguration>>, Box<dyn Error + Send + Sync>> {
387 for server_configuration in self.inner.iter().rev() {
392 if match_hostname(server_configuration.filters.hostname.as_deref(), hostname)
393 && ((server_configuration.filters.ip.is_none() && (!is_localhost(server_configuration.filters.ip.as_ref(), server_configuration.filters.hostname.as_deref())
394 || socket_data.local_addr.ip().to_canonical().is_loopback())) || server_configuration.filters.ip == Some(socket_data.local_addr.ip()))
396 && (server_configuration.filters.port.is_none()
397 || server_configuration.filters.port == Some(socket_data.local_addr.port()))
398 && server_configuration
399 .filters
400 .condition
401 .as_ref()
402 .map(|c| match_conditions(c, request, socket_data))
403 .unwrap_or(Ok(true))?
404 && server_configuration.filters.error_handler_status.is_none()
405 {
406 return Ok(Some(server_configuration.clone()));
407 }
408 }
409
410 Ok(None)
411 }
412
413 pub fn find_error_configuration(
415 &self,
416 filters: &ServerConfigurationFilters,
417 status_code: u16,
418 ) -> Option<Arc<ServerConfiguration>> {
419 self
420 .inner
421 .iter()
422 .rev()
423 .find(|c| {
424 c.filters.is_host
425 && c.filters.hostname == filters.hostname
426 && c.filters.ip == filters.ip
427 && c.filters.port == filters.port
428 && (c.filters.condition.is_none() || c.filters.condition == filters.condition)
429 && !c.filters.error_handler_status.as_ref().is_none_or(|s| {
430 !(matches!(s, ErrorHandlerStatus::Any) || matches!(s, ErrorHandlerStatus::Status(x) if *x == status_code))
431 })
432 })
433 .cloned()
434 }
435
436 pub fn find_global_configuration(&self) -> Option<Arc<ServerConfiguration>> {
438 let mut iterator = self.inner.iter();
440 let first_found = iterator.find(|server_configuration| {
441 server_configuration.filters.is_global() || server_configuration.filters.is_global_non_host()
442 });
443 if let Some(first_found) = first_found {
444 if first_found.filters.is_global() {
445 return Some(first_found.clone());
446 }
447 for server_configuration in iterator {
448 if server_configuration.filters.is_global() {
449 return Some(server_configuration.clone());
450 } else if !server_configuration.filters.is_global_non_host() {
451 return Some(first_found.clone());
452 }
453 }
454 }
455 None
456 }
457}