#![allow(unused_doc_comments)]
#[cfg(feature = "redis")]
pub mod log;
#[cfg(feature = "redis")]
pub mod redaction;
#[cfg(feature = "redis")]
pub mod regex;
#[cfg(feature = "redis")]
pub mod validation;
mod security_impl;
#[cfg(feature = "redis")]
#[allow(unused_imports)]
pub use log::{log_cache_key, sanitize_message};
#[cfg(feature = "redis")]
#[allow(unused_imports)]
pub use redaction::{Redacted, redact_cache_key, redact_connection_string, redact_field, redact_value};
#[cfg(feature = "redis")]
#[allow(unused_imports)]
pub use regex::{compile_glob_pattern, compile_regex, glob_to_regex, match_safe};
#[cfg(feature = "redis")]
#[allow(unused_imports)]
pub use validation::{
DANGEROUS_CHARS, MAX_KEY_LENGTH, validate_max_length, validate_no_dangerous_chars, validate_not_empty,
};
#[cfg(feature = "redis")]
use crate::error::OxCacheError;
#[cfg(feature = "redis")]
pub use security_impl::{clamp_scan_count, validate_lua_script, validate_redis_key, validate_scan_pattern};
#[cfg(feature = "redis")]
use security_impl::{
MAX_LUA_SCRIPT_KEYS, MAX_LUA_SCRIPT_LENGTH, MAX_SCAN_PATTERN_LENGTH, MAX_SCAN_WILDCARDS, SCAN_COUNT_MAX,
SCAN_COUNT_MIN, count_lua_long_string_level, preprocess_lua_script, skip_lua_long_string,
};
#[cfg(all(test, feature = "redis"))]
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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(OxCacheError::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(all(any(test, feature = "testing"), feature = "redis"))]
#[allow(unused_imports)]
pub mod test_helpers {
pub use super::{clamp_scan_count, validate_lua_script, validate_redis_key, validate_scan_pattern};
}