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
12pub struct ConditionMatchData<'a> {
14 pub request: &'a hyper::http::request::Parts,
15 pub socket_data: &'a SocketData,
16}
17
18pub 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
53fn 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}