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