waf_detection/crs/
operator.rs1use regex::Regex;
8
9use super::ast::Operator;
10
11#[derive(Debug)]
13pub enum Matcher {
14 Rx(Regex),
15 Pm(Vec<String>),
17 Contains(String),
18 BeginsWith(String),
19 EndsWith(String),
20 Within(String),
22 StrEq(String),
23}
24
25impl Matcher {
26 pub fn matches(&self, value: &str) -> bool {
28 match self {
29 Matcher::Rx(re) => re.is_match(value),
30 Matcher::Pm(phrases) => {
31 let lower = value.to_ascii_lowercase();
32 phrases.iter().any(|p| lower.contains(p.as_str()))
33 }
34 Matcher::Contains(s) => value.contains(s.as_str()),
35 Matcher::BeginsWith(s) => value.starts_with(s.as_str()),
36 Matcher::EndsWith(s) => value.ends_with(s.as_str()),
37 Matcher::Within(set) => set.contains(value),
38 Matcher::StrEq(s) => value == s,
39 }
40 }
41}
42
43pub fn compile(op: &Operator) -> Result<Matcher, String> {
46 match op {
47 Operator::Rx(pattern) => Regex::new(pattern)
48 .map(Matcher::Rx)
49 .map_err(|e| format!("invalid @rx regex: {e}")),
50 Operator::Pm(phrases) => {
51 Ok(Matcher::Pm(phrases.iter().map(|p| p.to_ascii_lowercase()).collect()))
52 }
53 Operator::PmFromFile(_) => {
56 Err("@pmFromFile not supported in v1 (use inline @pm)".to_string())
57 }
58 Operator::Contains(s) => Ok(Matcher::Contains(s.clone())),
59 Operator::BeginsWith(s) => Ok(Matcher::BeginsWith(s.clone())),
60 Operator::EndsWith(s) => Ok(Matcher::EndsWith(s.clone())),
61 Operator::Within(s) => Ok(Matcher::Within(s.clone())),
62 Operator::StrEq(s) => Ok(Matcher::StrEq(s.clone())),
63 Operator::Unsupported(name) => Err(format!("unsupported operator @{name}")),
64 }
65}
66
67pub fn rx_pattern(op: &Operator) -> Option<&str> {
69 match op {
70 Operator::Rx(p) => Some(p.as_str()),
71 _ => None,
72 }
73}