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