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