use wafrift_types::Signal;
use crate::signal_body_marker::{BLOCK_HEADER_NAMES, WAF_HEADERS};
pub fn classify_headers(headers: &[(String, String)]) -> Vec<Signal> {
let mut signals = Vec::new();
for (header_name, header_value) in headers {
let name_lower = header_name.to_ascii_lowercase();
let value_lower = header_value.to_ascii_lowercase();
for &(waf_header, value_pattern, description) in WAF_HEADERS {
if name_lower == waf_header {
if value_pattern.is_empty() || value_lower.contains(value_pattern) {
signals.push(Signal::BodyMarker(format!("header:{description}")));
}
}
}
if BLOCK_HEADER_NAMES.contains(&name_lower.as_str()) {
signals.push(Signal::BodyMarker(format!(
"waf_block_header:{}={}",
name_lower,
header_value.chars().take(64).collect::<String>()
)));
}
}
signals
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_cloudflare() {
let headers = vec![("cf-ray".to_string(), "abc123".to_string())];
let signals = classify_headers(&headers);
assert!(!signals.is_empty());
assert!(signals.iter().any(|s| {
if let Signal::BodyMarker(m) = s {
m.contains("cloudflare")
} else {
false
}
}));
}
#[test]
fn detects_aws_waf_block() {
let headers = vec![("x-amzn-waf-action".to_string(), "block".to_string())];
let signals = classify_headers(&headers);
assert!(
signals.len() >= 2,
"should detect both WAF header and block header"
);
}
#[test]
fn detects_imperva() {
let headers = vec![("x-iinfo".to_string(), "test123".to_string())];
let signals = classify_headers(&headers);
assert!(!signals.is_empty());
}
#[test]
fn ignores_unrelated_headers() {
let headers = vec![
("content-type".to_string(), "text/html".to_string()),
("content-length".to_string(), "1024".to_string()),
];
let signals = classify_headers(&headers);
assert!(signals.is_empty());
}
#[test]
fn case_insensitive_header_names() {
let headers = vec![("CF-Ray".to_string(), "abc".to_string())];
let signals = classify_headers(&headers);
assert!(!signals.is_empty(), "should match case-insensitively");
}
#[test]
fn value_pattern_matching() {
let headers = vec![("x-amzn-waf-action".to_string(), "allow".to_string())];
let signals = classify_headers(&headers);
let has_allow = signals.iter().any(|s| {
if let Signal::BodyMarker(m) = s {
m.contains("aws_waf_allow")
} else {
false
}
});
assert!(has_allow, "should detect AWS WAF allow action");
}
}