sentinel_modsec/engine/
chain.rs1use super::ruleset::CompiledRule;
4
5#[derive(Debug, Clone)]
7pub struct ChainState {
8 pub in_chain: bool,
10 pub chain_matched: bool,
12 pub captures: Vec<String>,
14 pub chain_start: Option<usize>,
16}
17
18impl ChainState {
19 pub fn new() -> Self {
21 Self {
22 in_chain: false,
23 chain_matched: false,
24 captures: Vec::new(),
25 chain_start: None,
26 }
27 }
28
29 pub fn start_chain(&mut self, rule_idx: usize) {
31 self.in_chain = true;
32 self.chain_matched = true;
33 self.chain_start = Some(rule_idx);
34 self.captures.clear();
35 }
36
37 pub fn continue_chain(&mut self, matched: bool, captures: &[String]) {
39 if matched {
40 self.captures.extend(captures.iter().cloned());
41 } else {
42 self.chain_matched = false;
43 }
44 }
45
46 pub fn end_chain(&mut self) -> bool {
48 let matched = self.chain_matched;
49 self.in_chain = false;
50 self.chain_matched = false;
51 self.chain_start = None;
52 self.captures.clear();
53 matched
54 }
55
56 pub fn reset(&mut self) {
58 self.in_chain = false;
59 self.chain_matched = false;
60 self.chain_start = None;
61 self.captures.clear();
62 }
63}
64
65impl Default for ChainState {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71pub fn evaluate_chain<F>(
73 rules: &[CompiledRule],
74 start_idx: usize,
75 mut eval_rule: F,
76) -> Option<(bool, Vec<String>)>
77where
78 F: FnMut(&CompiledRule) -> Option<(bool, Vec<String>)>,
79{
80 let mut state = ChainState::new();
81 let mut idx = start_idx;
82 let mut all_captures = Vec::new();
83
84 loop {
85 if idx >= rules.len() {
86 break;
87 }
88
89 let rule = &rules[idx];
90
91 match eval_rule(rule) {
93 Some((matched, captures)) => {
94 if matched {
95 all_captures.extend(captures);
96 if rule.is_chain {
97 if let Some(next_idx) = rule.chain_next {
98 idx = next_idx;
99 continue;
100 }
101 }
102 return Some((true, all_captures));
104 } else {
105 return Some((false, Vec::new()));
107 }
108 }
109 None => {
110 return None;
112 }
113 }
114 }
115
116 Some((false, Vec::new()))
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_chain_state() {
125 let mut state = ChainState::new();
126 assert!(!state.in_chain);
127
128 state.start_chain(0);
129 assert!(state.in_chain);
130 assert!(state.chain_matched);
131
132 state.continue_chain(true, &["test".to_string()]);
133 assert!(state.chain_matched);
134 assert_eq!(state.captures.len(), 1);
135
136 state.continue_chain(false, &[]);
137 assert!(!state.chain_matched);
138
139 let matched = state.end_chain();
140 assert!(!matched);
141 assert!(!state.in_chain);
142 }
143}