use lazy_static::lazy_static;
use regex::Regex;
use std::{collections::HashSet, sync::Arc};
lazy_static! {
static ref DEFAULT_PATTERNS: Vec<Regex> = {
vec![
Regex::new(r"(?i)(drop\s+table|delete\s+from|insert\s+into|select\s+\*|union\s+all|update\s+.*\s+set|--|;|\bexec\b)").unwrap(),
Regex::new(r"(?i)(<script>|javascript:|on\w+\s*=|alert\(|eval\(|document\.|window\.)").unwrap(),
Regex::new(r"(\.\./|\.\.\\|%2e%2e%2f|%2e%2e%5c)").unwrap(),
Regex::new(r"(?:%[0-9a-fA-F]{2}){2,}").unwrap(),
Regex::new(r"(?i)(\||&&|;|`|\$\(|\bexec\b|\bsystem\b|\brm\b|\bdel\b)").unwrap(),
]
};
}
#[derive(Debug, Clone)]
pub struct SecurityConfig {
pub forbidden_chars: Arc<HashSet<char>>,
pub blocked_patterns: Arc<Vec<Regex>>,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self::builder()
.with_default_forbidden_chars()
.with_default_blocked_patterns()
.build()
}
}
impl SecurityConfig {
pub fn builder() -> SecurityConfigBuilder {
SecurityConfigBuilder::new()
}
#[inline(always)]
pub fn is_char_forbidden(&self, c: &char) -> bool {
self.forbidden_chars.contains(c)
}
pub fn has_blocked_pattern(&self, input: &str) -> bool {
self.blocked_patterns.iter().any(|re| re.is_match(input))
}
}
#[derive(Debug, Default)]
pub struct SecurityConfigBuilder {
forbidden_chars: HashSet<char>,
blocked_patterns: Vec<Regex>,
}
impl SecurityConfigBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_default_forbidden_chars(mut self) -> Self {
self.forbidden_chars
.extend(['<', '>', '&', '\'', '"', '\\', ';', '`']);
self
}
pub fn with_default_blocked_patterns(mut self) -> Self {
self.blocked_patterns.extend(DEFAULT_PATTERNS.clone());
self
}
pub fn add_forbidden_char(mut self, c: char) -> Self {
self.forbidden_chars.insert(c);
self
}
pub fn add_blocked_pattern(mut self, pattern: &str) -> Result<Self, regex::Error> {
self.blocked_patterns.push(Regex::new(pattern)?);
Ok(self)
}
pub fn build(self) -> SecurityConfig {
SecurityConfig {
forbidden_chars: Arc::new(self.forbidden_chars),
blocked_patterns: Arc::new(self.blocked_patterns),
}
}
}