use crate::error::FlowGuardError;
use crate::matchers::RequestContext;
use ahash::AHashMap as HashMap;
use async_trait::async_trait;
use chrono::Timelike;
use serde_json::Value;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, error, info, warn};
const MAX_MATCHER_NAME_LENGTH: usize = 100;
const MAX_HEADER_NAME_LENGTH: usize = 256;
const MAX_HEADER_VALUE_LENGTH: usize = 4096;
const MAX_ALLOWED_VALUES_COUNT: usize = 100;
#[allow(dead_code)]
const MAX_REGEX_NESTING_DEPTH: usize = 10;
fn validate_matcher_name(name: &str) -> Result<(), FlowGuardError> {
if name.is_empty() {
return Err(FlowGuardError::ConfigError(
"匹配器名称不能为空".to_string(),
));
}
if name.len() > MAX_MATCHER_NAME_LENGTH {
return Err(FlowGuardError::ConfigError(format!(
"匹配器名称长度超过限制(最大 {} 字符)",
MAX_MATCHER_NAME_LENGTH
)));
}
if !name
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-')
{
return Err(FlowGuardError::ConfigError(
"匹配器名称只能包含字母、数字、下划线和连字符".to_string(),
));
}
Ok(())
}
fn validate_header_name(name: &str) -> Result<(), FlowGuardError> {
if name.is_empty() {
return Err(FlowGuardError::ConfigError(
"HTTP头名称不能为空".to_string(),
));
}
if name.len() > MAX_HEADER_NAME_LENGTH {
return Err(FlowGuardError::ConfigError(format!(
"HTTP头名称长度超过限制(最大 {} 字符)",
MAX_HEADER_NAME_LENGTH
)));
}
if !name.chars().all(|c| c.is_alphanumeric() || c == '-') {
return Err(FlowGuardError::ConfigError(
"HTTP头名称只能包含字母、数字和连字符".to_string(),
));
}
Ok(())
}
fn validate_header_value(value: &str) -> Result<(), FlowGuardError> {
if value.len() > MAX_HEADER_VALUE_LENGTH {
return Err(FlowGuardError::ConfigError(format!(
"HTTP头值长度超过限制(最大 {} 字符)",
MAX_HEADER_VALUE_LENGTH
)));
}
Ok(())
}
#[allow(dead_code)]
fn validate_regex_complexity(pattern: &str) -> Result<(), FlowGuardError> {
if pattern.len() > 1000 {
return Err(FlowGuardError::ConfigError(
"正则表达式模式过长(最大 1000 字符)".to_string(),
));
}
let mut depth: usize = 0;
let mut max_depth: usize = 0;
for c in pattern.chars() {
match c {
'(' => {
depth += 1;
max_depth = max_depth.max(depth);
}
')' => {
depth = depth.saturating_sub(1);
}
_ => {}
}
}
if max_depth > MAX_REGEX_NESTING_DEPTH {
return Err(FlowGuardError::ConfigError(format!(
"正则表达式嵌套深度过大(最大 {})",
MAX_REGEX_NESTING_DEPTH
)));
}
let dangerous_patterns = [
"(.+)+",
"(.+)*",
"(.+){2,}",
"(.+){2,}",
"([a-z]+)+",
"([a-z]+)*",
"([a-z]+){2,}",
"(\\d+)+",
"(\\d+)*",
"(\\d+){2,}",
"(\\w+)+",
"(\\w+)*",
"(\\w+){2,}",
"(.+?)+",
"(.+?)*",
"(.+?){2,}",
"([a-z]+?)+",
"([a-z]+?)*",
"([a-z]+?){2,}",
"(.*+)+",
"(.*+)*",
"(.*+){2,}",
"([a-zA-Z]+)+",
"([0-9]+)+",
"([\\w\\d]+)+",
"(a+)+",
"(b+)+",
"(c+)+",
"(x+)+",
"(y+)+",
"(z+)+",
"[a-z]+[a-z]+",
"[0-9]+[0-9]+",
"**",
"++",
"??",
"{,}",
"{,2}",
"{2,}",
"{2,1}",
];
for dangerous in &dangerous_patterns {
if pattern.contains(dangerous) {
return Err(FlowGuardError::ConfigError(format!(
"正则表达式包含危险模式(可能导致 ReDoS 攻击): {}",
dangerous
)));
}
}
if pattern.matches("+*?{}").count() > pattern.len() / 2 {
return Err(FlowGuardError::ConfigError(
"正则表达式包含过多量词,可能存在性能问题".to_string(),
));
}
#[cfg(feature = "advanced-matchers")]
{
use regex::Regex;
Regex::new(pattern)
.map_err(|e| FlowGuardError::ConfigError(format!("无效的正则表达式: {}", e)))?;
}
Ok(())
}
#[allow(dead_code)]
fn sanitize_string(input: &str) -> String {
input
.chars()
.filter(|c| c.is_alphanumeric() || c.is_whitespace() || "-_.".contains(*c))
.collect()
}
#[async_trait]
pub trait CustomMatcher: Send + Sync {
fn name(&self) -> &str;
async fn matches(&self, context: &RequestContext) -> Result<bool, FlowGuardError>;
fn load_config(&mut self, config: Value) -> Result<(), FlowGuardError>;
}
#[derive(Clone)]
pub struct CustomMatcherRegistry {
matchers: Arc<RwLock<HashMap<String, Box<dyn CustomMatcher>>>>,
}
impl std::fmt::Debug for CustomMatcherRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CustomMatcherRegistry")
.field("matchers", &"<custom matchers>")
.finish()
}
}
impl CustomMatcherRegistry {
pub fn new() -> Self {
Self {
matchers: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn register(
&self,
name: String,
matcher: Box<dyn CustomMatcher>,
) -> Result<(), FlowGuardError> {
validate_matcher_name(&name)?;
let mut matchers = self.matchers.write().await;
if matchers.contains_key(&name) {
let error_msg = format!("匹配器 '{}' 已存在", name);
warn!("{}", error_msg);
return Err(FlowGuardError::ConfigError(error_msg));
}
info!("注册自定义匹配器: {}", name);
matchers.insert(name.clone(), matcher);
debug!("当前注册的匹配器数量: {}", matchers.len());
Ok(())
}
pub async fn get(&self, name: &str) -> Option<Box<dyn CustomMatcher>> {
let matchers = self.matchers.read().await;
if let Some(_matcher) = matchers.get(name) {
debug!("查询匹配器: {}", name);
None
} else {
debug!("未找到匹配器: {}", name);
None
}
}
pub async fn contains(&self, name: &str) -> bool {
let matchers = self.matchers.read().await;
matchers.contains_key(name)
}
pub async fn unregister(&self, name: &str) -> Result<(), FlowGuardError> {
let mut matchers = self.matchers.write().await;
if !matchers.contains_key(name) {
let error_msg = format!("匹配器 '{}' 不存在", name);
warn!("{}", error_msg);
return Err(FlowGuardError::ConfigError(error_msg));
}
info!("注销自定义匹配器: {}", name);
matchers.remove(name);
debug!("当前注册的匹配器数量: {}", matchers.len());
Ok(())
}
#[allow(clippy::map_clone)]
pub async fn list(&self) -> Vec<String> {
let matchers = self.matchers.read().await;
matchers.keys().map(|k| k.clone()).collect()
}
pub async fn count(&self) -> usize {
let matchers = self.matchers.read().await;
matchers.len()
}
pub async fn clear(&self) {
let mut matchers = self.matchers.write().await;
info!("清空所有自定义匹配器");
matchers.clear();
}
pub async fn match_with(
&self,
name: &str,
context: &RequestContext,
) -> Result<bool, FlowGuardError> {
let matchers = self.matchers.read().await;
let matcher = matchers.get(name).ok_or_else(|| {
let error_msg = format!("匹配器 '{}' 不存在", name);
error!("{}", error_msg);
FlowGuardError::ConfigError(error_msg)
})?;
debug!("使用匹配器 '{}' 检查请求", name);
matcher.matches(context).await
}
}
impl Default for CustomMatcherRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TimeWindowMatcher {
start_hour: u8,
end_hour: u8,
}
impl TimeWindowMatcher {
pub fn new(start_hour: u8, end_hour: u8) -> Self {
assert!(start_hour <= 23, "开始小时必须在 0-23 范围内");
assert!(end_hour <= 23, "结束小时必须在 0-23 范围内");
Self {
start_hour,
end_hour,
}
}
pub fn start_hour(&self) -> u8 {
self.start_hour
}
pub fn end_hour(&self) -> u8 {
self.end_hour
}
}
#[async_trait]
impl CustomMatcher for TimeWindowMatcher {
fn name(&self) -> &str {
"time_window"
}
async fn matches(&self, _context: &RequestContext) -> Result<bool, FlowGuardError> {
let now = chrono::Utc::now();
let hour = now.hour() as u8;
let matches = if self.start_hour <= self.end_hour {
hour >= self.start_hour && hour <= self.end_hour
} else {
hour >= self.start_hour || hour <= self.end_hour
};
debug!(
"时间窗口匹配: 当前时间 {}小时, 窗口 {}-{}小时, 结果: {}",
hour, self.start_hour, self.end_hour, matches
);
Ok(matches)
}
fn load_config(&mut self, config: Value) -> Result<(), FlowGuardError> {
let start_hour = config["start_hour"]
.as_u64()
.ok_or_else(|| FlowGuardError::ConfigError("缺少 start_hour 配置".to_string()))?
as u8;
let end_hour = config["end_hour"]
.as_u64()
.ok_or_else(|| FlowGuardError::ConfigError("缺少 end_hour 配置".to_string()))?
as u8;
if start_hour > 23 {
return Err(FlowGuardError::ConfigError(
"start_hour 必须在 0-23 范围内".to_string(),
));
}
if end_hour > 23 {
return Err(FlowGuardError::ConfigError(
"end_hour 必须在 0-23 范围内".to_string(),
));
}
self.start_hour = start_hour;
self.end_hour = end_hour;
info!(
"加载时间窗口匹配器配置: {}-{}小时",
self.start_hour, self.end_hour
);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct HeaderMatcher {
header_name: String,
allowed_values: Vec<String>,
case_sensitive: bool,
}
impl HeaderMatcher {
pub fn new(header_name: &str, allowed_values: Vec<String>) -> Result<Self, FlowGuardError> {
validate_header_name(header_name)?;
if allowed_values.len() > MAX_ALLOWED_VALUES_COUNT {
return Err(FlowGuardError::ValidationError(format!(
"允许的值数量超过限制(最大 {})",
MAX_ALLOWED_VALUES_COUNT
)));
}
for value in &allowed_values {
validate_header_value(value)?;
}
Ok(Self {
header_name: header_name.to_lowercase(),
allowed_values,
case_sensitive: false,
})
}
pub fn with_case_sensitive(mut self, case_sensitive: bool) -> Self {
self.case_sensitive = case_sensitive;
self
}
pub fn header_name(&self) -> &str {
&self.header_name
}
pub fn allowed_values(&self) -> &[String] {
&self.allowed_values
}
}
#[async_trait]
impl CustomMatcher for HeaderMatcher {
fn name(&self) -> &str {
"header"
}
async fn matches(&self, context: &RequestContext) -> Result<bool, FlowGuardError> {
let header_value = match context.get_header(&self.header_name) {
Some(value) => value,
None => {
debug!("HTTP头 '{}' 不存在", self.header_name);
return Ok(false);
}
};
let matches = if self.case_sensitive {
self.allowed_values.contains(header_value)
} else {
let lower_value = header_value.to_lowercase();
self.allowed_values
.iter()
.any(|v| v.to_lowercase() == lower_value)
};
debug!(
"HTTP头匹配: 头='{}', 值='{}', 结果: {}",
self.header_name, header_value, matches
);
Ok(matches)
}
fn load_config(&mut self, config: Value) -> Result<(), FlowGuardError> {
if let Some(header_name) = config["header_name"].as_str() {
validate_header_name(header_name)?;
self.header_name = header_name.to_lowercase();
}
if let Some(values) = config["allowed_values"].as_array() {
if values.len() > MAX_ALLOWED_VALUES_COUNT {
return Err(FlowGuardError::ConfigError(format!(
"允许的值数量超过限制(最大 {})",
MAX_ALLOWED_VALUES_COUNT
)));
}
self.allowed_values = values
.iter()
.filter_map(|v| v.as_str())
.map(|s| {
validate_header_value(s)?;
Ok(s.to_string())
})
.collect::<Result<Vec<_>, FlowGuardError>>()?;
}
if let Some(case_sensitive) = config["case_sensitive"].as_bool() {
self.case_sensitive = case_sensitive;
}
info!(
"加载HTTP头匹配器配置: 头='{}', 允许值={:?}, 区分大小写={}",
self.header_name, self.allowed_values, self.case_sensitive
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::matchers::RequestContext;
#[tokio::test]
async fn test_registry_new() {
let registry = CustomMatcherRegistry::new();
assert_eq!(registry.count().await, 0);
}
#[tokio::test]
async fn test_registry_register() {
let registry = CustomMatcherRegistry::new();
let matcher = TimeWindowMatcher::new(9, 18);
assert!(registry
.register("time_window".to_string(), Box::new(matcher))
.await
.is_ok());
assert_eq!(registry.count().await, 1);
assert!(registry.contains("time_window").await);
}
#[tokio::test]
async fn test_registry_register_duplicate() {
let registry = CustomMatcherRegistry::new();
let matcher = TimeWindowMatcher::new(9, 18);
assert!(registry
.register("time_window".to_string(), Box::new(matcher))
.await
.is_ok());
let result = registry
.register(
"time_window".to_string(),
Box::new(TimeWindowMatcher::new(10, 20)),
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_registry_unregister() {
let registry = CustomMatcherRegistry::new();
let matcher = TimeWindowMatcher::new(9, 18);
registry
.register("time_window".to_string(), Box::new(matcher))
.await
.unwrap();
assert!(registry.unregister("time_window").await.is_ok());
assert_eq!(registry.count().await, 0);
assert!(!registry.contains("time_window").await);
}
#[tokio::test]
async fn test_registry_unregister_nonexistent() {
let registry = CustomMatcherRegistry::new();
let result = registry.unregister("nonexistent").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_registry_list() {
let registry = CustomMatcherRegistry::new();
registry
.register(
"matcher1".to_string(),
Box::new(TimeWindowMatcher::new(9, 18)),
)
.await
.unwrap();
registry
.register(
"matcher2".to_string(),
Box::new(HeaderMatcher::new("X-API-Key", vec!["secret".to_string()]).unwrap()),
)
.await
.unwrap();
let list = registry.list().await;
assert_eq!(list.len(), 2);
assert!(list.contains(&"matcher1".to_string()));
assert!(list.contains(&"matcher2".to_string()));
}
#[tokio::test]
async fn test_registry_clear() {
let registry = CustomMatcherRegistry::new();
registry
.register(
"matcher1".to_string(),
Box::new(TimeWindowMatcher::new(9, 18)),
)
.await
.unwrap();
registry.clear().await;
assert_eq!(registry.count().await, 0);
}
#[tokio::test]
async fn test_registry_match_with() {
let registry = CustomMatcherRegistry::new();
let matcher = TimeWindowMatcher::new(0, 23);
registry
.register("time_window".to_string(), Box::new(matcher))
.await
.unwrap();
let context = RequestContext::new();
let result = registry.match_with("time_window", &context).await;
assert!(result.is_ok());
assert!(result.unwrap());
}
#[tokio::test]
async fn test_registry_match_with_nonexistent() {
let registry = CustomMatcherRegistry::new();
let context = RequestContext::new();
let result = registry.match_with("nonexistent", &context).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_time_window_matcher_new() {
let matcher = TimeWindowMatcher::new(9, 18);
assert_eq!(matcher.name(), "time_window");
assert_eq!(matcher.start_hour(), 9);
assert_eq!(matcher.end_hour(), 18);
}
#[tokio::test]
async fn test_time_window_matcher_matches() {
let matcher = TimeWindowMatcher::new(0, 23); let context = RequestContext::new();
let result = matcher.matches(&context).await.unwrap();
assert!(result);
}
#[tokio::test]
async fn test_time_window_matcher_load_config() {
let mut matcher = TimeWindowMatcher::new(9, 18);
let config = serde_json::json!({
"start_hour": 10,
"end_hour": 20
});
assert!(matcher.load_config(config).is_ok());
assert_eq!(matcher.start_hour(), 10);
assert_eq!(matcher.end_hour(), 20);
}
#[tokio::test]
async fn test_time_window_matcher_load_config_invalid() {
let mut matcher = TimeWindowMatcher::new(9, 18);
let config = serde_json::json!({
"start_hour": 25
});
let result = matcher.load_config(config);
assert!(result.is_err());
}
#[test]
#[should_panic(expected = "开始小时必须在 0-23 范围内")]
fn test_time_window_matcher_invalid_start_hour() {
TimeWindowMatcher::new(25, 18);
}
#[test]
#[should_panic(expected = "结束小时必须在 0-23 范围内")]
fn test_time_window_matcher_invalid_end_hour() {
TimeWindowMatcher::new(9, 25);
}
#[tokio::test]
async fn test_header_matcher_new() {
let matcher = HeaderMatcher::new("X-API-Key", vec!["secret123".to_string()]).unwrap();
assert_eq!(matcher.name(), "header");
assert_eq!(matcher.header_name(), "x-api-key");
assert_eq!(matcher.allowed_values().len(), 1);
}
#[tokio::test]
async fn test_header_matcher_matches() {
let matcher = HeaderMatcher::new("X-API-Key", vec!["secret123".to_string()]).unwrap();
let context = RequestContext::new().with_header("X-API-Key", "secret123");
let result = matcher.matches(&context).await.unwrap();
assert!(result);
}
#[tokio::test]
async fn test_header_matcher_not_matches() {
let matcher = HeaderMatcher::new("X-API-Key", vec!["secret123".to_string()]).unwrap();
let context = RequestContext::new().with_header("X-API-Key", "wrong");
let result = matcher.matches(&context).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_header_matcher_missing_header() {
let matcher = HeaderMatcher::new("X-API-Key", vec!["secret123".to_string()]).unwrap();
let context = RequestContext::new();
let result = matcher.matches(&context).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_header_matcher_case_insensitive() {
let matcher = HeaderMatcher::new("X-API-Key", vec!["SECRET123".to_string()]).unwrap();
let context = RequestContext::new().with_header("X-API-Key", "secret123");
let result = matcher.matches(&context).await.unwrap();
assert!(result);
}
#[tokio::test]
async fn test_header_header_matcher_case_sensitive() {
let matcher = HeaderMatcher::new("X-API-Key", vec!["SECRET123".to_string()])
.unwrap()
.with_case_sensitive(true);
let context = RequestContext::new().with_header("X-API-Key", "secret123");
let result = matcher.matches(&context).await.unwrap();
assert!(!result);
}
#[tokio::test]
async fn test_header_matcher_multiple_values() {
let matcher = HeaderMatcher::new(
"X-API-Key",
vec!["secret123".to_string(), "secret456".to_string()],
)
.unwrap();
let context1 = RequestContext::new().with_header("X-API-Key", "secret123");
let context2 = RequestContext::new().with_header("X-API-Key", "secret456");
assert!(matcher.matches(&context1).await.unwrap());
assert!(matcher.matches(&context2).await.unwrap());
}
#[tokio::test]
async fn test_header_matcher_load_config() {
let mut matcher = HeaderMatcher::new("X-API-Key", vec!["secret123".to_string()]).unwrap();
let config = serde_json::json!({
"header_name": "Authorization",
"allowed_values": ["Bearer token123"],
"case_sensitive": true
});
assert!(matcher.load_config(config).is_ok());
assert_eq!(matcher.header_name(), "authorization");
assert_eq!(matcher.allowed_values().len(), 1);
}
#[tokio::test]
async fn test_registry_concurrent_register() {
let registry = Arc::new(CustomMatcherRegistry::new());
let mut handles = vec![];
for i in 0..10 {
let registry_clone = Arc::clone(®istry);
handles.push(tokio::spawn(async move {
let matcher = TimeWindowMatcher::new(i as u8, (i + 10) as u8);
registry_clone
.register(format!("matcher_{}", i), Box::new(matcher))
.await
}));
}
let mut success_count = 0;
for handle in handles {
if handle.await.unwrap().is_ok() {
success_count += 1;
}
}
assert_eq!(success_count, 10);
assert_eq!(registry.count().await, 10);
}
#[tokio::test]
async fn test_registry_concurrent_match() {
let registry = Arc::new(CustomMatcherRegistry::new());
let matcher = TimeWindowMatcher::new(0, 23);
registry
.register("time_window".to_string(), Box::new(matcher))
.await
.unwrap();
let mut handles = vec![];
for _ in 0..100 {
let registry_clone = Arc::clone(®istry);
handles.push(tokio::spawn(async move {
let context = RequestContext::new();
registry_clone.match_with("time_window", &context).await
}));
}
let mut success_count = 0;
for handle in handles {
if let Ok(Ok(true)) = handle.await {
success_count += 1;
}
}
assert_eq!(success_count, 100);
}
}