use crate::emitter::escape::{escape_shell_string, escape_variable_name};
use std::process::Command;
use tempfile::TempDir;
#[test]
fn test_unicode_emoji_no_injection() {
let test_cases = vec![
"Hello 👋 World",
"🔥💯✨",
"Test 😀😁😂🤣",
"Mixed text with 🚀 emoji",
];
for input in test_cases {
let escaped = escape_shell_string(input);
assert!(
escaped.starts_with('\'') || escaped.starts_with('"'),
"Emoji string not quoted: {} -> {}",
input,
escaped
);
let result = execute_shell_echo(&escaped);
assert_eq!(
result.trim(),
input,
"Emoji roundtrip failed: {} -> {} -> {}",
input,
escaped,
result
);
}
}
#[test]
fn test_unicode_cjk_characters_safe() {
let test_cases = vec![
"你好世界", "こんにちは", "コンニチハ", "안녕하세요", "Mixed English 中文",
];
for input in test_cases {
let escaped = escape_shell_string(input);
let result = execute_shell_echo(&escaped);
assert_eq!(
result.trim(),
input,
"CJK roundtrip failed: {} -> {} -> {}",
input,
escaped,
result
);
}
}
#[test]
fn test_unicode_rtl_languages_safe() {
let test_cases = vec![
"مرحبا", "שלום", "Mixed مرحبا English",
];
for input in test_cases {
let escaped = escape_shell_string(input);
let result = execute_shell_echo(&escaped);
assert_eq!(
result.trim(),
input,
"RTL roundtrip failed: {} -> {} -> {}",
input,
escaped,
result
);
}
}
#[test]
fn test_unicode_combining_characters_safe() {
let test_cases = vec![
"café", "naïve", "Zürich", "e\u{0301}", ];
for input in test_cases {
let escaped = escape_shell_string(input);
let result = execute_shell_echo(&escaped);
assert_eq!(
result.trim(),
input,
"Combining char roundtrip failed: {} -> {} -> {}",
input,
escaped,
result
);
}
}
#[test]
fn test_unicode_control_characters_safe() {
let test_cases = vec![
"line1\nline2", "tab\there", "null\0byte", "bell\x07", "escape\x1b[0m", ];
for input in test_cases {
let escaped = escape_shell_string(input);
assert!(
escaped.contains('\'') || escaped.contains('"'),
"Control characters not quoted: {:?} -> {}",
input,
escaped
);
let result = execute_shell_echo(&escaped);
assert!(
!result.contains("rm -rf"),
"Possible injection with control chars: {:?} -> {}",
input,
result
);
}
}
#[test]
fn test_unicode_zero_width_characters_safe() {
let test_cases = vec![
"test\u{200B}invisible", "test\u{200C}joiner", "test\u{200D}joiner", "test\u{FEFF}bom", ];
for input in test_cases {
let escaped = escape_shell_string(input);
let result = execute_shell_echo(&escaped);
assert!(
!result.contains(';'),
"Possible injection with zero-width chars: {:?} -> {}",
input,
result
);
}
}
#[test]
fn test_unicode_normalization_forms_safe() {
let nfc = "café"; let nfd = "café";
let escaped_nfc = escape_shell_string(nfc);
let escaped_nfd = escape_shell_string(nfd);
let result_nfc = execute_shell_echo(&escaped_nfc);
let result_nfd = execute_shell_echo(&escaped_nfd);
assert_eq!(result_nfc.trim(), nfc, "NFC roundtrip failed");
assert_eq!(result_nfd.trim(), nfd, "NFD roundtrip failed");
}
#[test]
fn test_unicode_with_shell_metacharacters() {
let test_cases = vec![
"Hello $USER 你好",
"Path: $(pwd) 🚀",
"Injection; rm -rf / 😈",
"`backtick` attack 中文",
"Pipe | redirect > 한글",
];
for input in test_cases {
let escaped = escape_shell_string(input);
assert!(
escaped.starts_with('\''),
"Shell metacharacters with unicode not quoted: {} -> {}",
input,
escaped
);
let result = execute_shell_echo(&escaped);
assert!(
!result.contains("root") && !result.contains("bin"),
"Possible command execution: {} -> {}",
input,
result
);
}
}
#[test]
fn test_unicode_long_strings_safe() {
let long_emoji = "🚀".repeat(1000);
let escaped = escape_shell_string(&long_emoji);
assert!(escaped.len() > 0, "Long emoji string produced empty escape");
let short_emoji = "🚀".repeat(10);
let escaped_short = escape_shell_string(&short_emoji);
let result = execute_shell_echo(&escaped_short);
assert_eq!(result.trim(), short_emoji, "Long emoji roundtrip failed");
}
#[test]
fn test_unicode_variable_names_sanitized() {
let test_cases = vec![
("hello_世界", "hello___"), ("test_🚀", "test__"), ("café_var", "caf__var"), ("_valid", "_valid"), ("123_invalid", "_23_invalid"), ];
for (input, expected) in test_cases {
let result = escape_variable_name(input);
assert_eq!(
result, expected,
"Variable name escaping failed: {} -> {} (expected {})",
input, result, expected
);
assert!(
is_valid_shell_identifier(&result),
"Escaped variable name not valid: {}",
result
);
}
}
#[test]
fn test_unicode_bidi_override_safe() {
let test_cases = vec![
"test\u{202E}esrever", "normal\u{202D}forced_ltr", "embed\u{202A}rtl\u{202C}end", ];
for input in test_cases {
let escaped = escape_shell_string(input);
assert!(
escaped.contains('\''),
"Bidi characters not quoted: {:?} -> {}",
input,
escaped
);
let result = execute_shell_echo(&escaped);
assert!(
result.trim().len() > 0,
"Bidi test failed: empty result for {:?}",
input
);
}
}
fn execute_shell_echo(escaped: &str) -> String {
let temp_dir = TempDir::new().unwrap();
let script_path = temp_dir.path().join("test.sh");
let script = format!("#!/bin/sh\necho {}\n", escaped);
std::fs::write(&script_path, script).unwrap();
let output = Command::new("sh")
.arg(&script_path)
.output()
.expect("Failed to execute shell");
if !output.status.success() {
panic!(
"Shell execution failed for escaped string: {}\nStderr: {}",
escaped,
String::from_utf8_lossy(&output.stderr)
);
}
String::from_utf8_lossy(&output.stdout).to_string()
}
fn is_valid_shell_identifier(name: &str) -> bool {
if name.is_empty() {
return false;
}
let first_char = name.chars().next().unwrap();
if !first_char.is_ascii_alphabetic() && first_char != '_' {
return false;
}
name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
#[cfg(test)] #[test]
fn test_unicode_fuzzing_random_strings() {
use rand::Rng;
let mut rng = rand::rng();
for _ in 0..100 {
let len = rng.random_range(1..50);
let random_string: String = (0..len)
.map(|_| {
let codepoint = rng.random_range(0x0000..0x10FFFF);
char::from_u32(codepoint).unwrap_or('?')
})
.collect();
let escaped = escape_shell_string(&random_string);
assert!(escaped.len() > 0, "Empty escape for: {:?}", random_string);
if !escaped.starts_with('\'') {
assert!(
!random_string.contains('$')
&& !random_string.contains('`')
&& !random_string.contains(';'),
"Dangerous chars unquoted: {:?} -> {}",
random_string,
escaped
);
}
}
}