use super::phase::Phase;
use crate::actions::RuleMetadata;
#[derive(Debug, Clone)]
pub struct Intervention {
pub status: u16,
pub url: Option<String>,
pub log: Option<String>,
pub rule_ids: Vec<String>,
pub phase: Phase,
pub drop_connection: bool,
pub metadata: Vec<RuleMetadata>,
}
impl Intervention {
pub fn new(status: u16, phase: Phase) -> Self {
Self {
status,
url: None,
log: None,
rule_ids: Vec::new(),
phase,
drop_connection: false,
metadata: Vec::new(),
}
}
pub fn deny(status: u16, phase: Phase, rule_id: Option<String>) -> Self {
let mut intervention = Self::new(status, phase);
if let Some(id) = rule_id {
intervention.rule_ids.push(id);
}
intervention
}
pub fn redirect(url: String, phase: Phase, rule_id: Option<String>) -> Self {
let mut intervention = Self::new(302, phase);
intervention.url = Some(url);
if let Some(id) = rule_id {
intervention.rule_ids.push(id);
}
intervention
}
pub fn drop(phase: Phase, rule_id: Option<String>) -> Self {
let mut intervention = Self::new(444, phase);
intervention.drop_connection = true;
if let Some(id) = rule_id {
intervention.rule_ids.push(id);
}
intervention
}
pub fn add_rule_id(&mut self, id: String) {
self.rule_ids.push(id);
}
pub fn add_metadata(&mut self, metadata: RuleMetadata) {
if let Some(ref id) = metadata.id {
self.rule_ids.push(id.clone());
}
if let Some(ref msg) = metadata.msg {
if self.log.is_none() {
self.log = Some(msg.clone());
}
}
self.metadata.push(metadata);
}
pub fn set_log(&mut self, log: String) {
self.log = Some(log);
}
pub fn format_log(&self) -> String {
let mut parts = vec![format!("[status {}]", self.status)];
if !self.rule_ids.is_empty() {
parts.push(format!("[rule_ids: {}]", self.rule_ids.join(", ")));
}
if let Some(ref log) = self.log {
parts.push(format!("[msg: {}]", log));
}
if let Some(ref url) = self.url {
parts.push(format!("[redirect: {}]", url));
}
parts.push(format!("[phase: {}]", self.phase.name()));
parts.join(" ")
}
}
impl Default for Intervention {
fn default() -> Self {
Self::new(403, Phase::RequestHeaders)
}
}
#[derive(Debug, Clone)]
pub struct InterventionBuilder {
intervention: Intervention,
}
impl InterventionBuilder {
pub fn new() -> Self {
Self {
intervention: Intervention::default(),
}
}
pub fn status(mut self, status: u16) -> Self {
self.intervention.status = status;
self
}
pub fn phase(mut self, phase: Phase) -> Self {
self.intervention.phase = phase;
self
}
pub fn redirect(mut self, url: String) -> Self {
self.intervention.status = 302;
self.intervention.url = Some(url);
self
}
pub fn drop_connection(mut self) -> Self {
self.intervention.drop_connection = true;
self.intervention.status = 444;
self
}
pub fn rule_id(mut self, id: String) -> Self {
self.intervention.rule_ids.push(id);
self
}
pub fn log(mut self, msg: String) -> Self {
self.intervention.log = Some(msg);
self
}
pub fn build(self) -> Intervention {
self.intervention
}
}
impl Default for InterventionBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deny_intervention() {
let intervention = Intervention::deny(403, Phase::RequestHeaders, Some("12345".to_string()));
assert_eq!(intervention.status, 403);
assert_eq!(intervention.rule_ids, vec!["12345".to_string()]);
assert!(!intervention.drop_connection);
}
#[test]
fn test_redirect_intervention() {
let intervention = Intervention::redirect(
"https://example.com/blocked".to_string(),
Phase::RequestHeaders,
Some("12345".to_string()),
);
assert_eq!(intervention.status, 302);
assert_eq!(
intervention.url,
Some("https://example.com/blocked".to_string())
);
}
#[test]
fn test_builder() {
let intervention = InterventionBuilder::new()
.status(403)
.phase(Phase::RequestBody)
.rule_id("100".to_string())
.log("SQL Injection detected".to_string())
.build();
assert_eq!(intervention.status, 403);
assert_eq!(intervention.phase, Phase::RequestBody);
assert_eq!(intervention.rule_ids, vec!["100".to_string()]);
}
#[test]
fn test_format_log() {
let intervention = Intervention::deny(403, Phase::RequestHeaders, Some("942100".to_string()));
let log = intervention.format_log();
assert!(log.contains("[status 403]"));
assert!(log.contains("942100"));
}
}