#![allow(unused_doc_comments)]
pub mod log;
pub mod redaction;
pub mod regex;
pub mod validation;
#[allow(unused_imports)]
pub use log::{log_cache_key, sanitize_message};
#[allow(unused_imports)]
pub use redaction::{redact_cache_key, redact_connection_string, redact_field, redact_value, Redacted};
#[allow(unused_imports)]
pub use regex::{compile_glob_pattern, compile_regex, glob_to_regex, match_safe};
#[allow(unused_imports)]
pub use validation::{validate_max_length, validate_no_dangerous_chars, validate_not_empty};
use crate::error::{CacheError, Result};
const MAX_LUA_SCRIPT_LENGTH: usize = 10 * 1024;
const MAX_LUA_SCRIPT_KEYS: usize = 100;
const MAX_SCAN_PATTERN_LENGTH: usize = 256;
const MAX_SCAN_WILDCARDS: usize = 10;
const SCAN_COUNT_MIN: usize = 1;
const SCAN_COUNT_MAX: usize = 1000;
static LUA_LOOP_PATTERNS: &[(&str, &str)] = &[
(r"WHILE\s+TRUE", "WHILE TRUE 循环"),
(r"WHILE\s+1", "WHILE 1 循环"),
(r"REPEAT", "REPEAT 循环"),
(r"GOTO", "GOTO 语句"),
];
lazy_static::lazy_static! {
static ref LUA_LOOP_REGEXES: Vec<::regex::Regex> = {
LUA_LOOP_PATTERNS
.iter()
.map(|(pattern, _)| ::regex::Regex::new(pattern).expect("Invalid loop pattern regex"))
.collect()
};
}
lazy_static::lazy_static! {
static ref WHITESPACE_REGEX: ::regex::Regex = ::regex::Regex::new(r"\s+").expect("Invalid whitespace regex");
}
#[cfg_attr(docsrs, doc(cfg(feature = "security")))]
pub fn validate_redis_key(key: &str) -> Result<()> {
use crate::security::validation::redis::{DANGEROUS_CHARS, MAX_KEY_LENGTH};
use crate::security::validation::{validate_max_length, validate_no_dangerous_chars, validate_not_empty};
validate_not_empty(key, "Redis key")?;
validate_max_length(key, MAX_KEY_LENGTH, "Redis key")?;
validate_no_dangerous_chars(key, &DANGEROUS_CHARS, "Redis key")?;
for c in key.chars() {
if c.is_control() && !DANGEROUS_CHARS.contains(&c) && c != '\t' {
return Err(CacheError::InvalidInput(format!(
"Redis key contains control character: U+{:04X}",
c as u32
)));
}
}
const SQL_INJECTION_PATTERNS: &[(&str, &str)] = &[
("' OR '", "单引号后跟 OR 模式"),
("'--", "SQL 注释模式"),
("'; DROP", "SQL DROP 语句"),
("'; DELETE", "SQL DELETE 语句"),
("'; INSERT", "SQL INSERT 语句"),
("UNION SELECT", "SQL UNION 查询"),
("xp_cmdshell", "SQL Server 命令执行"),
("' OR '1'='1", "经典 SQL 注入永真条件"),
("admin'--", "SQL 认证绕过"),
];
let key_upper = key.to_uppercase();
for (pattern, description) in SQL_INJECTION_PATTERNS {
if key_upper.contains(&pattern.to_uppercase()) {
return Err(CacheError::InvalidInput(format!(
"Redis key contains suspicious SQL injection pattern: {}",
description
)));
}
}
const PATH_TRAVERSAL_PATTERNS: &[&str] = &[
"../",
"..\\",
"%2e%2e",
"%252e%252e",
"..%2f",
"..%5c",
"%2e%2e%2f",
"%2e%2e%5c",
];
for pattern in PATH_TRAVERSAL_PATTERNS {
if key.to_lowercase().contains(&pattern.to_lowercase()) {
return Err(CacheError::InvalidInput(format!(
"Redis key contains path traversal pattern: {}",
pattern
)));
}
}
const COMMAND_INJECTION_CHARS: &[char] = &[';', '|', '&', '`'];
for c in key.chars() {
if COMMAND_INJECTION_CHARS.contains(&c) {
return Err(CacheError::InvalidInput(format!(
"Redis key contains potential command injection character: {:?}",
c
)));
}
}
Ok(())
}
#[cfg_attr(docsrs, doc(cfg(feature = "security")))]
pub fn validate_lua_script(script: &str, key_count: usize) -> Result<()> {
if script.len() > MAX_LUA_SCRIPT_LENGTH {
return Err(CacheError::InvalidInput(format!(
"Lua script exceeds maximum length of {} bytes (got {} bytes)",
MAX_LUA_SCRIPT_LENGTH,
script.len()
)));
}
if key_count > MAX_LUA_SCRIPT_KEYS {
return Err(CacheError::InvalidInput(format!(
"Lua script exceeds maximum key count of {} (got {} keys)",
MAX_LUA_SCRIPT_KEYS, key_count
)));
}
let cleaned = preprocess_lua_script(script);
let cleaned_upper = cleaned.to_uppercase();
let forbidden_patterns = [
("REDIS.CALL('FLUSHALL')", "FLUSHALL"),
("REDIS.CALL(\"FLUSHALL\")", "FLUSHALL"),
("REDIS.PCALL('FLUSHALL')", "FLUSHALL via PCALL"),
("REDIS.PCALL(\"FLUSHALL\")", "FLUSHALL via PCALL"),
("REDIS.CALL('FLUSHDB')", "FLUSHDB"),
("REDIS.CALL(\"FLUSHDB\")", "FLUSHDB"),
("REDIS.PCALL('FLUSHDB')", "FLUSHDB via PCALL"),
("REDIS.PCALL(\"FLUSHDB\")", "FLUSHDB via PCALL"),
("REDIS.CALL('KEYS'", "KEYS"),
("REDIS.CALL(\"KEYS\"", "KEYS"),
("REDIS.PCALL('KEYS'", "KEYS via PCALL"),
("REDIS.PCALL(\"KEYS\"", "KEYS via PCALL"),
("REDIS.CALL('SHUTDOWN')", "SHUTDOWN"),
("REDIS.CALL(\"SHUTDOWN\")", "SHUTDOWN"),
("REDIS.CALL('CONFIG'", "CONFIG"),
("REDIS.CALL(\"CONFIG\"", "CONFIG"),
("REDIS.CALL('DEBUG'", "DEBUG"),
("REDIS.CALL(\"DEBUG\"", "DEBUG"),
("REDIS.CALL('SAVE')", "SAVE"),
("REDIS.CALL(\"SAVE\")", "SAVE"),
("REDIS.CALL('BGSAVE')", "BGSAVE"),
("REDIS.CALL(\"BGSAVE\")", "BGSAVE"),
("REDIS.CALL('MONITOR')", "MONITOR"),
("REDIS.CALL(\"MONITOR\")", "MONITOR"),
("OS.EXECUTE", "os.execute"),
("OS.EXEC", "os.exec"),
("IO.POPEN", "io.popen"),
("LOADSTRING", "loadstring"),
("LOAD(", "load()"),
];
for (pattern, description) in &forbidden_patterns {
if cleaned_upper.contains(pattern) {
return Err(CacheError::InvalidInput(format!(
"Lua script contains forbidden pattern: {}",
description
)));
}
}
if cleaned_upper.contains("REDIS.EVAL") || cleaned_upper.contains("REDIS.EVALSHA") {
return Err(CacheError::InvalidInput(
"Lua script contains nested redis.eval/evalsha".to_string(),
));
}
for re in LUA_LOOP_REGEXES.iter() {
if re.is_match(&cleaned_upper) {
return Err(CacheError::InvalidInput(
"Lua script contains potential infinite loop patterns".to_string(),
));
}
}
Ok(())
}
fn preprocess_lua_script(script: &str) -> String {
let mut result = String::with_capacity(script.len());
let mut chars = script.chars().peekable();
while let Some(c) = chars.next() {
if c == '-' && chars.peek() == Some(&'-') {
chars.next();
let level = count_lua_long_string_level(&mut chars, 1);
if level > 0 {
skip_lua_long_string(&mut chars, level);
} else {
while let Some(&next_c) = chars.peek() {
if next_c == '\n' {
break;
}
chars.next();
}
}
} else if c == '[' {
let level = count_lua_long_string_level(&mut chars, 0);
if level > 0 {
skip_lua_long_string(&mut chars, level);
} else {
result.push('[');
}
} else if c == '"' {
result.push('"');
result.push('"'); while let Some(&next_c) = chars.peek() {
if next_c == '"' {
chars.next(); break;
} else if next_c == '\\' {
chars.next(); chars.next();
} else if next_c == '\n' {
break; } else {
chars.next();
}
}
} else if c == '\'' {
result.push('\'');
let mut in_string = true;
while in_string {
if let Some(&next_c) = chars.peek() {
if next_c == '\'' {
chars.next();
result.push('\'');
in_string = false;
} else if next_c == '\\' {
chars.next();
if let Some(escaped) = chars.next() {
if escaped.is_alphanumeric() || escaped == '_' {
result.push(escaped);
}
}
} else if next_c == '\n' {
break;
} else if next_c.is_alphanumeric() || next_c == '_' {
result.push(next_c);
chars.next();
} else {
chars.next();
}
} else {
break;
}
}
} else if c.is_whitespace() {
if !result.is_empty() && !result.ends_with(' ') {
result.push(' ');
}
} else {
result.push(c);
}
}
WHITESPACE_REGEX.replace_all(&result, " ").to_string()
}
fn count_lua_long_string_level(chars: &mut std::iter::Peekable<std::str::Chars>, start_level: usize) -> usize {
let mut level = start_level;
while let Some(&c) = chars.peek() {
if c == '=' {
level += 1;
chars.next();
} else if c == '[' {
chars.next();
return level; } else {
break; }
}
0 }
fn skip_lua_long_string(chars: &mut std::iter::Peekable<std::str::Chars>, level: usize) {
let closing: String = format!("]{}{}]", "=".repeat(level - 1), "=".repeat(level - 1));
let closing_chars: Vec<char> = closing.chars().collect();
let mut pos = 0;
let closing_len = closing.len();
while let Some(c) = chars.next() {
if c == ']' {
let mut check_pos = 1;
let mut is_match = true;
while check_pos < closing_len {
if let Some(&next_c) = chars.peek() {
if next_c == closing_chars[check_pos] {
chars.next();
check_pos += 1;
} else {
is_match = false;
break;
}
} else {
is_match = false;
break;
}
}
if is_match && check_pos == closing_len {
break; }
}
pos += 1;
if pos > 1_000_000 {
break; }
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "security")))]
pub fn validate_scan_pattern(pattern: &str) -> Result<()> {
if pattern.len() > MAX_SCAN_PATTERN_LENGTH {
return Err(CacheError::InvalidInput(format!(
"SCAN pattern exceeds maximum length of {} characters (got {} characters)",
MAX_SCAN_PATTERN_LENGTH,
pattern.len()
)));
}
let wildcard_count = pattern.chars().filter(|c| *c == '*').count();
if wildcard_count > MAX_SCAN_WILDCARDS {
return Err(CacheError::InvalidInput(format!(
"SCAN pattern contains too many wildcards (max {}, got {})",
MAX_SCAN_WILDCARDS, wildcard_count
)));
}
Ok(())
}
#[cfg_attr(docsrs, doc(cfg(feature = "security")))]
pub fn clamp_scan_count(count: usize) -> usize {
count.clamp(SCAN_COUNT_MIN, SCAN_COUNT_MAX)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_redis_key_valid() {
assert!(validate_redis_key("user:123").is_ok());
assert!(validate_redis_key("cache:data:value").is_ok());
assert!(validate_redis_key("test_key").is_ok());
}
#[test]
fn test_validate_redis_key_empty() {
let result = validate_redis_key("");
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_redis_key_too_long() {
let key = "x".repeat(512 * 1024 + 1);
let result = validate_redis_key(&key);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_redis_key_contains_crlf() {
assert!(validate_redis_key("key\r\n").is_err());
assert!(validate_redis_key("key\rvalue").is_err());
assert!(validate_redis_key("key\nvalue").is_err());
}
#[test]
fn test_validate_redis_key_contains_null() {
assert!(validate_redis_key("key\0value").is_err());
}
#[test]
fn test_validate_lua_script_valid() {
let script = "return redis.call('GET', KEYS[1])";
assert!(
validate_lua_script(script, 1).is_ok(),
"Valid Lua script should not return error"
);
}
#[test]
fn test_validate_lua_script_too_long() {
let script = "x".repeat(MAX_LUA_SCRIPT_LENGTH + 1);
let result = validate_lua_script(&script, 1);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_lua_script_too_many_keys() {
let script = "return redis.call('GET', KEYS[1])";
let result = validate_lua_script(script, MAX_LUA_SCRIPT_KEYS + 1);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_lua_script_flushall() {
let script = "return redis.call('FLUSHALL')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_lua_script_flushdb() {
let script = "return redis.call('FLUSHDB')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_lua_script_keys_command() {
let script = "return redis.call('KEYS', '*')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_lua_script_shutdown() {
let script = "return redis.call('SHUTDOWN')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_lua_script_case_insensitive() {
let script = "return redis.call('flushall')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_lua_script_safe_commands() {
let script = r#"
local val = redis.call('GET', KEYS[1])
if val then
redis.call('SETEX', KEYS[2], 60, val)
end
return val
"#;
assert!(validate_lua_script(script, 2).is_ok());
}
#[test]
fn test_validate_scan_pattern_valid() {
assert!(validate_scan_pattern("user:*").is_ok());
assert!(validate_scan_pattern("session:*:data").is_ok());
assert!(validate_scan_pattern("cache?").is_ok());
}
#[test]
fn test_validate_scan_pattern_too_long() {
let pattern = "x".repeat(MAX_SCAN_PATTERN_LENGTH + 1);
let result = validate_scan_pattern(&pattern);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_scan_pattern_too_many_wildcards() {
let pattern = "*".repeat(MAX_SCAN_WILDCARDS + 1);
let result = validate_scan_pattern(&pattern);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_validate_scan_pattern_exact_wildcard_limit() {
let pattern = "*".repeat(MAX_SCAN_WILDCARDS);
assert!(validate_scan_pattern(&pattern).is_ok());
}
#[test]
fn test_clamp_scan_count() {
assert_eq!(clamp_scan_count(0), SCAN_COUNT_MIN);
assert_eq!(clamp_scan_count(500), 500);
assert_eq!(clamp_scan_count(1000), SCAN_COUNT_MAX);
assert_eq!(clamp_scan_count(2000), SCAN_COUNT_MAX);
}
#[test]
fn test_sql_injection_patterns() {
assert!(validate_redis_key("' OR '1'='1").is_err());
assert!(validate_redis_key("'; DROP TABLE--").is_err());
}
#[test]
fn test_sql_injection_false_positive_prevention() {
assert!(validate_redis_key("order_status").is_ok());
assert!(validate_redis_key("user_data_123").is_ok());
assert!(validate_redis_key("api_response").is_ok());
}
#[test]
fn test_command_injection_patterns() {
assert!(validate_redis_key("some_long_key_name;ls").is_err());
assert!(validate_redis_key("some_long_key_name|cat").is_err());
assert!(validate_redis_key("some_long_key_name&whoami").is_err());
assert!(validate_redis_key("key;value").is_err());
assert!(validate_redis_key("key|value").is_err());
}
#[test]
fn test_path_traversal_patterns() {
assert!(validate_redis_key("../etc/passwd").is_err());
assert!(validate_redis_key("..\\windows\\system32").is_err());
}
#[test]
fn test_unicode_control_characters() {
assert!(validate_redis_key("key\u{0001}value").is_err());
assert!(validate_redis_key("key\u{007F}value").is_err());
}
#[test]
fn test_lua_script_edge_cases() {
let script = "return 1";
assert!(validate_lua_script(script, 0).is_ok());
let script = "";
assert!(validate_lua_script(script, 0).is_ok());
let script = "return redis.call('FLUSHALL')";
assert!(validate_lua_script(script, 0).is_err());
}
#[test]
fn test_lua_script_comment_bypass_prevention() {
let script = "--[[ FLUSHALL ]] return 1";
assert!(validate_lua_script(script, 0).is_ok());
let script = "return 1 -- FLUSHALL";
assert!(validate_lua_script(script, 0).is_ok());
}
#[test]
fn test_scan_pattern_edge_cases() {
assert!(validate_scan_pattern("*").is_ok());
assert!(validate_scan_pattern("?").is_ok());
assert!(validate_scan_pattern("[a-z]").is_ok());
assert!(validate_scan_pattern("").is_ok());
}
#[test]
fn test_redis_key_max_length_boundary() {
let max_key = "x".repeat(512 * 1024);
assert!(validate_redis_key(&max_key).is_ok());
let over_max_key = "x".repeat(512 * 1024 + 1);
assert!(validate_redis_key(&over_max_key).is_err());
}
#[test]
fn test_lua_script_max_length_boundary() {
let max_script = "x".repeat(MAX_LUA_SCRIPT_LENGTH);
assert!(validate_lua_script(&max_script, 1).is_ok());
let over_max_script = "x".repeat(MAX_LUA_SCRIPT_LENGTH + 1);
assert!(validate_lua_script(&over_max_script, 1).is_err());
}
#[test]
fn test_lua_script_nested_eval() {
let script = "return redis.eval('return 1', KEYS[1])";
let result = validate_lua_script(script, 1);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_lua_script_nested_evalsha() {
let script = "return redis.evalsha(sha, KEYS[1])";
let result = validate_lua_script(script, 1);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_lua_script_while_true_loop() {
let script = "while true do end";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_lua_script_while_1_loop() {
let script = "while 1 do end";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_lua_script_repeat_loop() {
let script = "repeat until false";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_lua_script_goto_statement() {
let script = "goto label";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
assert!(matches!(result, Err(CacheError::InvalidInput(_))));
}
#[test]
fn test_lua_script_with_bracket_not_long_string() {
let script = "local x = table[1]";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_double_quoted_string() {
let script = "local x = \"hello world\"";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_escaped_chars_in_string() {
let script = "local x = \"hello\\nworld\"";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_string_regular_chars() {
let script = "local x = \"regular text here\"";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_single_quoted_escape() {
let script = "local x = 'hello\\nworld'";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_single_quoted_alphanumeric() {
let script = "local x = 'abc123_def'";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_single_quoted_non_alphanumeric() {
let script = "local x = 'a-b-c'";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_long_string_level() {
let script = "local x = [==[hello]==]";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_long_string_comment() {
let script = "--[[ this is a comment ]] return 1";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_long_string_comment_level() {
let script = "--[=[ this is a comment ]=] return 1";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_unclosed_long_string() {
let script = "local x = [[hello] world]]";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_partial_closing_bracket() {
let script = "local x = [==[hello]=] world]==]";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_multiline_long_string() {
let script = "local x = [[line1\nline2\nline3]]";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_backslash_escape_in_single_quote() {
let script = "local x = 'a\\nb'";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_backslash_escape_non_alphanumeric() {
let script = "local x = 'a\\-b'";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_unclosed_single_quote_string() {
let script = "local x = 'unclosed\nreturn 1";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_unclosed_double_quote_string() {
let script = "local x = \"unclosed\nreturn 1";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_backslash_at_end_of_double_quote() {
let script = "local x = \"a\\b\"";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_lua_script_with_bracket_after_equals() {
let script = "local x = [= 1";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
#[test]
fn test_preprocess_lua_script_directly() {
assert_eq!(preprocess_lua_script("return 1"), "return 1");
assert_eq!(preprocess_lua_script("-- comment\nreturn 1"), "return 1");
assert_eq!(preprocess_lua_script("local x = 1"), "local x = 1");
}
#[test]
fn test_preprocess_lua_script_with_strings() {
let result = preprocess_lua_script("local x = \"test\"");
assert!(result.contains("\""));
}
#[test]
fn test_preprocess_lua_script_with_single_quotes() {
let result = preprocess_lua_script("local x = 'test'");
assert!(result.contains("'"));
}
#[test]
fn test_preprocess_lua_script_with_long_strings() {
let result = preprocess_lua_script("local x = [[content]]");
assert!(result.contains("content"));
assert!(result.contains("["));
assert!(result.contains("]]"));
}
#[test]
fn test_preprocess_lua_script_with_whitespace_normalization() {
let result = preprocess_lua_script("local x = 1");
assert!(result.contains("local x = 1"));
}
#[test]
fn test_count_lua_long_string_level() {
let mut chars = "==[test".chars().peekable();
let level = count_lua_long_string_level(&mut chars, 0);
assert_eq!(level, 2);
}
#[test]
fn test_count_lua_long_string_level_no_equals() {
let mut chars = "[test".chars().peekable();
let level = count_lua_long_string_level(&mut chars, 0);
assert_eq!(level, 0);
}
#[test]
fn test_count_lua_long_string_level_not_long_string() {
let mut chars = "abc".chars().peekable();
let level = count_lua_long_string_level(&mut chars, 0);
assert_eq!(level, 0);
}
#[test]
fn test_skip_lua_long_string_basic() {
let mut chars = "content]]rest".chars().peekable();
skip_lua_long_string(&mut chars, 1);
let remaining: String = chars.collect();
assert_eq!(remaining, "rest");
}
#[test]
fn test_skip_lua_long_string_with_level() {
let mut chars = "content]==]rest".chars().peekable();
skip_lua_long_string(&mut chars, 2);
let remaining: String = chars.collect();
assert_eq!(remaining, "rest");
}
#[test]
fn test_skip_lua_long_string_with_partial_closing() {
let mut chars = "content]=]rest]==]end".chars().peekable();
skip_lua_long_string(&mut chars, 2);
let remaining: String = chars.collect();
assert_eq!(remaining, "end");
}
#[test]
fn test_skip_lua_long_string_no_closing() {
let mut chars = "content without closing".chars().peekable();
skip_lua_long_string(&mut chars, 1);
let remaining: String = chars.collect();
assert_eq!(remaining, "");
}
#[test]
fn test_validate_scan_pattern_exact_length_limit() {
let pattern = "x".repeat(MAX_SCAN_PATTERN_LENGTH);
assert!(validate_scan_pattern(&pattern).is_ok());
}
#[test]
fn test_clamp_scan_count_min_boundary() {
assert_eq!(clamp_scan_count(1), 1);
}
#[test]
fn test_redis_key_with_tab_character() {
assert!(validate_redis_key("key\tvalue").is_ok());
}
#[test]
fn test_redis_key_with_backtick() {
assert!(validate_redis_key("key`value").is_err());
}
#[test]
fn test_redis_key_with_sql_union_select() {
assert!(validate_redis_key("UNION SELECT").is_err());
}
#[test]
fn test_redis_key_with_sql_xp_cmdshell() {
assert!(validate_redis_key("xp_cmdshell").is_err());
}
#[test]
fn test_redis_key_with_sql_admin_bypass() {
assert!(validate_redis_key("admin'--").is_err());
}
#[test]
fn test_redis_key_with_url_encoded_path_traversal() {
assert!(validate_redis_key("%2e%2e%2f").is_err());
assert!(validate_redis_key("%252e%252e").is_err());
assert!(validate_redis_key("..%2f").is_err());
assert!(validate_redis_key("..%5c").is_err());
assert!(validate_redis_key("%2e%2e%5c").is_err());
}
#[test]
fn test_redis_key_with_sql_insert_pattern() {
assert!(validate_redis_key("'; INSERT").is_err());
}
#[test]
fn test_redis_key_with_sql_delete_pattern() {
assert!(validate_redis_key("'; DELETE").is_err());
}
#[test]
fn test_redis_key_with_sql_comment_pattern() {
assert!(validate_redis_key("'--").is_err());
}
#[test]
fn test_lua_script_with_os_execute() {
let script = "os.execute('rm -rf /')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_os_exec() {
let script = "os.exec('cmd')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_io_popen() {
let script = "io.popen('ls')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_loadstring() {
let script = "loadstring('return 1')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_load() {
let script = "load('return 1')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_config_command() {
let script = "redis.call('CONFIG', 'GET', '*')";
let result = validate_lua_script(script, 1);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_debug_command() {
let script = "redis.call('DEBUG', 'SLEEP', 0)";
let result = validate_lua_script(script, 1);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_save_command() {
let script = "redis.call('SAVE')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_bgsave_command() {
let script = "redis.call('BGSAVE')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_monitor_command() {
let script = "redis.call('MONITOR')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_pcall_flushall() {
let script = "redis.pcall('FLUSHALL')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_pcall_flushdb() {
let script = "redis.pcall('FLUSHDB')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_pcall_keys() {
let script = "redis.pcall('KEYS', '*')";
let result = validate_lua_script(script, 0);
assert!(result.is_err());
}
#[test]
fn test_lua_script_with_double_quoted_commands() {
let script = "redis.call(\"FLUSHALL\")";
let result = validate_lua_script(script, 0);
assert!(result.is_ok());
}
}
#[cfg(any(test, feature = "testing"))]
#[allow(unused_imports)]
pub mod test_helpers {
pub use super::{clamp_scan_count, validate_lua_script, validate_redis_key, validate_scan_pattern};
}