1use regex::Regex;
4
5pub mod data;
6pub mod file;
7pub mod injection;
8pub mod protocol;
9pub mod result;
10pub mod scanner;
11
12pub use result::{AttackCategory, DetectionResult, Severity};
13pub use scanner::{Scanner, ScannerBuilder};
14
15pub trait Detector: Send + Sync {
16 fn name(&self) -> &'static str;
17 fn detect(&self, input: &str) -> Option<DetectionResult>;
18}
19
20pub(crate) fn regex_detect(
21 patterns: &[Regex],
22 name: &'static str,
23 category: AttackCategory,
24 severity: Severity,
25 message: &'static str,
26 input: &str,
27) -> Option<DetectionResult> {
28 for re in patterns {
29 if let Some(m) = re.find(input) {
30 return Some(DetectionResult {
31 attack_type: name.to_string(),
32 category,
33 severity,
34 matched_pattern: m.as_str().to_string(),
35 offset: m.start(),
36 message: message.into(),
37 });
38 }
39 }
40 None
41}
42
43#[cfg(test)]
44pub(crate) mod test_helpers {
45 use super::*;
46
47 pub(crate) fn assert_detected<D: Detector>(
48 d: &D,
49 input: &str,
50 category: AttackCategory,
51 severity: Severity,
52 ) {
53 let r = d.detect(input).expect("expected detection");
54 assert_eq!(r.attack_type, d.name());
55 assert_eq!(r.category, category);
56 assert_eq!(r.severity, severity);
57 assert!(!r.matched_pattern.is_empty(), "matched_pattern empty");
58 assert!(
59 r.offset <= input.len(),
60 "offset {} > len {}",
61 r.offset,
62 input.len()
63 );
64 assert_eq!(
65 &input[r.offset..r.offset + r.matched_pattern.len()],
66 r.matched_pattern
67 );
68 assert!(!r.message.is_empty());
69 }
70
71 pub(crate) fn assert_clean<D: Detector>(d: &D, input: &str) {
72 assert!(d.detect(input).is_none(), "not detected: {input:?}");
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn detector_trait_object_is_send_sync() {
82 let detector: Box<dyn Detector> = Box::new(injection::XssDetector);
83 assert_eq!(detector.name(), "xss");
84 }
85
86 #[test]
87 fn detector_name_is_static_str() {
88 let name: &'static str = injection::XssDetector.name();
89 assert_eq!(name, "xss");
90 }
91}