1use std::path::Path;
2
3use actix_http::Response;
4use actix_web::{
5 HttpMessage, HttpRequest, HttpResponse,
6 body::{BodyStream, BoxBody, to_bytes_limited},
7 dev::{Payload, ServiceRequest},
8 http::{StatusCode, Version, header},
9};
10
11use crate::{error::Error, factory::Middleware};
12
13const CONNECTION_INFO: &str = concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"));
14
15pub type Addr = (String, u16);
16
17#[derive(Clone, Default)]
18struct TransactionConfig {
19 max_request_body: Option<usize>,
20 max_response_body: Option<usize>,
21 server_address: Option<Addr>,
22}
23
24pub struct ModSecurity {
26 config: TransactionConfig,
27 rules: modsecurity::Rules,
28 security: modsecurity::ModSecurity,
29}
30
31impl ModSecurity {
32 pub fn new() -> Self {
40 Self {
41 config: TransactionConfig::default(),
42 rules: modsecurity::Rules::new(),
43 security: modsecurity::ModSecurity::builder()
44 .with_connector_info(CONNECTION_INFO)
45 .expect("failed to add connector into")
46 .build(),
47 }
48 }
49
50 pub fn add_rules(&mut self, rules: &str) -> Result<&mut Self, Error> {
61 self.rules.add_plain(rules)?;
62 Ok(self)
63 }
64
65 pub fn add_rules_file<P: AsRef<Path>>(&mut self, file: P) -> Result<&mut Self, Error> {
76 self.rules.add_file(file)?;
77 Ok(self)
78 }
79
80 pub fn set_max_request_size(&mut self, max_request_body: Option<usize>) -> &mut Self {
85 self.config.max_request_body = max_request_body;
86 self
87 }
88
89 pub fn set_max_response_size(&mut self, max_response_body: Option<usize>) -> &mut Self {
94 self.config.max_response_body = max_response_body;
95 self
96 }
97
98 pub fn set_server_address(&mut self, server_address: Option<Addr>) -> &mut Self {
103 self.config.server_address = server_address;
104 self
105 }
106
107 pub fn transaction(&self) -> Result<Transaction, Error> {
109 Ok(Transaction {
110 config: self.config.clone(),
111 transaction: self
112 .security
113 .transaction_builder()
114 .with_rules(&self.rules)
115 .build()?,
116 })
117 }
118
119 #[inline]
134 pub fn middleware(self) -> Middleware {
135 self.into()
136 }
137}
138
139impl Into<Middleware> for ModSecurity {
140 #[inline]
141 fn into(self) -> Middleware {
142 Middleware::new(self)
143 }
144}
145
146#[inline]
147fn version_str(v: Version) -> &'static str {
148 match v {
149 Version::HTTP_09 => "0.9",
150 Version::HTTP_10 => "1.0",
151 Version::HTTP_11 => "1.1",
152 Version::HTTP_2 => "2",
153 Version::HTTP_3 => "3",
154 _ => panic!("unexpected http version!"),
155 }
156}
157
158#[inline]
159fn intervention_response(intv: &modsecurity::Intervention) -> Result<HttpResponse, Error> {
160 if let Some(log) = intv.log() {
161 tracing::error!("{log}");
162 }
163 if let Some(url) = intv.url() {
164 let mut res = HttpResponse::TemporaryRedirect();
165 res.insert_header((header::LOCATION, url));
166 return Ok(res.into());
167 }
168 let code = StatusCode::from_u16(intv.status() as u16)?;
169 return Ok(HttpResponse::new(code));
170}
171
172pub struct Transaction<'a> {
174 config: TransactionConfig,
175 transaction: modsecurity::Transaction<'a>,
176}
177
178impl<'a> Transaction<'a> {
179 pub fn process_connection(&mut self, req: &HttpRequest) -> Result<(), Error> {
186 let Some(caddr) = req.peer_addr() else {
187 tracing::warn!("missing client-address. cannot scan connection");
188 return Ok(());
189 };
190 let Some(saddr) = self.config.server_address.as_ref() else {
191 tracing::warn!("missing server-address. cannot scan connection");
192 return Ok(());
193 };
194 Ok(self.transaction.process_connection(
195 &caddr.ip().to_string(),
196 caddr.port() as i32,
197 &saddr.0,
198 saddr.1 as i32,
199 )?)
200 }
201
202 #[inline]
209 pub fn process_uri(&mut self, req: &HttpRequest) -> Result<(), Error> {
210 Ok(self.transaction.process_uri(
211 &req.uri().to_string(),
212 req.method().as_str(),
213 version_str(req.version()),
214 )?)
215 }
216
217 #[inline]
224 pub fn process_request_headers(&mut self, req: &HttpRequest) -> Result<(), Error> {
225 req.headers()
226 .iter()
227 .filter_map(|(k, v)| Some((k.as_str(), v.to_str().ok()?)))
228 .try_for_each(|(k, v)| self.transaction.add_request_header(k, v))?;
229 Ok(self.transaction.process_request_headers()?)
230 }
231
232 pub async fn process_request_body(&mut self, payload: Payload) -> Result<Payload, Error> {
239 let max = self.config.max_request_body.unwrap_or(u16::MAX as usize);
240 let stream = BodyStream::new(payload);
241 let body = to_bytes_limited(stream, max).await??;
242 self.transaction.append_request_body(&body)?;
243 self.transaction.process_request_body()?;
244
245 let (_, mut payload) = actix_http::h1::Payload::create(true);
246 payload.unread_data(body);
247 Ok(Payload::H1 { payload })
248 }
249
250 pub async fn process_request(&mut self, req: &mut ServiceRequest) -> Result<(), Error> {
263 self.process_connection(req.request())?;
264 self.process_uri(req.request())?;
265 self.process_request_headers(req.request())?;
266 let payload = self.process_request_body(req.take_payload()).await?;
267 req.set_payload(payload);
268 Ok(())
269 }
270
271 pub fn process_response_headers<T>(&mut self, res: &HttpResponse<T>) -> Result<(), Error> {
276 let code: u16 = res.status().into();
277 let version = format!("HTTP {}", version_str(res.head().version));
278 res.headers()
279 .iter()
280 .filter_map(|(k, v)| Some((k.as_str(), v.to_str().ok()?)))
281 .try_for_each(|(k, v)| self.transaction.add_response_header(k, v))?;
282 Ok(self
283 .transaction
284 .process_response_headers(code as i32, &version)?)
285 }
286
287 pub async fn process_response_body(&mut self, body: BoxBody) -> Result<BoxBody, Error> {
292 let max = self.config.max_response_body.unwrap_or(u16::MAX as usize);
293 let body = to_bytes_limited(body, max).await??;
294 self.transaction.append_response_body(&body)?;
295 self.transaction.process_response_body()?;
296 Ok(BoxBody::new(body))
297 }
298
299 pub async fn process_response(&mut self, res: HttpResponse) -> Result<HttpResponse, Error> {
310 let (http_res, mut body) = res.into_parts();
311 self.process_response_headers(&http_res)?;
312 body = self.process_response_body(body).await?;
313 Ok(http_res.set_body(body))
314 }
315
316 pub fn intervention(&mut self) -> Result<Option<Intervention>, Error> {
321 let Some(intv) = self.transaction.intervention() else {
322 return Ok(None);
323 };
324 let response = intervention_response(&intv)?;
325 Ok(Some(Intervention {
326 message: intv.log().map(|s| s.to_owned()),
327 url: intv.url().map(|u| u.to_owned()),
328 code: StatusCode::from_u16(intv.status() as u16)?,
329 response,
330 }))
331 }
332}
333
334#[derive(Debug)]
337pub struct Intervention {
338 message: Option<String>,
339 url: Option<String>,
340 code: StatusCode,
341 response: HttpResponse<BoxBody>,
342}
343
344impl Intervention {
345 #[inline]
347 pub fn log(&self) -> Option<&str> {
348 self.message.as_ref().map(|s| s.as_str())
349 }
350
351 #[inline]
353 pub fn url(&self) -> Option<&str> {
354 self.url.as_ref().map(|s| s.as_str())
355 }
356
357 #[inline]
359 pub fn status(&self) -> StatusCode {
360 self.code
361 }
362
363 pub fn response(&self) -> &HttpResponse {
365 &self.response
366 }
367}
368
369impl Into<HttpResponse> for Intervention {
370 fn into(self) -> HttpResponse {
371 self.response
372 }
373}
374
375impl Into<Response<BoxBody>> for Intervention {
376 fn into(self) -> Response<BoxBody> {
377 self.response.into()
378 }
379}