#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
#![allow(clippy::indexing_slicing)]
#![allow(clippy::disallowed_methods)]
#![allow(clippy::panic)]
use std::fs;
use std::path::Path;
use crate::sqli::{SqliFlags, SqliTokenizer, Token, TokenType};
#[derive(Debug)]
struct TestCase {
name: String,
input: Vec<u8>, expected: String,
}
fn parse_test_file(raw_bytes: &[u8]) -> Option<TestCase> {
let mut state = 0; let mut test_name = String::new();
let mut input_bytes = Vec::new();
let mut expected = String::new();
let mut line_start = 0;
let mut first_input_line = true;
for (i, &byte) in raw_bytes.iter().enumerate() {
if byte == b'\n' || i == raw_bytes.len() - 1 {
let line_end = if byte == b'\n' { i } else { i + 1 };
let line_bytes = &raw_bytes[line_start..line_end];
let line_str = if state == 2 {
String::new()
} else {
String::from_utf8_lossy(line_bytes).trim_end().to_string()
};
match state {
0 => {
if line_str == "--TEST--" {
state = 1;
}
}
1 => {
if line_str == "--INPUT--" {
state = 2;
first_input_line = true;
} else if !line_str.is_empty() {
test_name.push_str(&line_str);
}
}
2 => {
let line_str = String::from_utf8_lossy(line_bytes).to_string();
if line_str.trim() == "--EXPECTED--" {
state = 3;
} else {
if !first_input_line {
input_bytes.push(b'\n');
}
first_input_line = false;
input_bytes.extend_from_slice(line_bytes);
}
}
3 => {
if !line_str.is_empty() {
if !expected.is_empty() {
expected.push('\n');
}
expected.push_str(&line_str);
}
}
_ => {}
}
line_start = i + 1;
}
}
if state == 3 {
Some(TestCase {
name: test_name,
input: input_bytes,
expected,
})
} else {
None
}
}
fn token_type_to_char(token_type: TokenType) -> char {
token_type.to_char()
}
fn format_token(token: &Token) -> String {
let type_char = token_type_to_char(token.token_type);
let value = format_token_value(token);
if value.is_empty() {
format!("{}", type_char)
} else {
format!("{} {}", type_char, value)
}
}
fn format_token_value(token: &Token) -> String {
match token.token_type {
TokenType::String => format_string_token(token),
TokenType::Variable => format_variable_token(token),
_ => token.value_as_str().to_string(),
}
}
fn format_string_token(token: &Token) -> String {
let mut result = String::new();
if token.str_open != 0 {
result.push(token.str_open as char);
}
result.push_str(token.value_as_str());
if token.str_close != 0 {
result.push(token.str_close as char);
}
result
}
fn format_variable_token(token: &Token) -> String {
let mut result = String::new();
if token.str_open != 0 {
for _ in 0..token.count {
result.push('@');
}
result.push(token.str_open as char);
result.push_str(token.value_as_str());
if token.str_close != 0 {
result.push(token.str_close as char);
}
} else {
result.push_str(token.value_as_str());
}
result
}
fn run_sqli_tokenization(input: &[u8]) -> String {
let flags = SqliFlags::FLAG_SQL_ANSI;
let mut tokenizer = SqliTokenizer::new(input, flags);
let mut result = Vec::new();
while let Some(token) = tokenizer.next_token() {
result.push(format_token(&token));
}
result.join("\n")
}
fn run_single_tokens_test(file_path: &Path) -> Result<(), String> {
let bytes = fs::read(file_path)
.map_err(|e| format!("Failed to read file {:?}: {}", file_path, e))?;
let test_case = parse_test_file(&bytes)
.ok_or_else(|| format!("Failed to parse test file {:?}", file_path))?;
let actual = run_sqli_tokenization(&test_case.input);
if actual != test_case.expected {
let input_display = String::from_utf8_lossy(&test_case.input);
return Err(format!(
"Test failed for {:?}\nTest: {}\nInput: {:?} (bytes: {:?})\nExpected:\n{}\nActual:\n{}",
file_path, test_case.name, input_display, test_case.input, test_case.expected, actual
));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_all_tokens_files() {
let test_dir = "../libinjection-c/tests";
if !Path::new(test_dir).exists() {
panic!("Test directory {} does not exist. Make sure libinjection-c submodule is initialized.", test_dir);
}
let entries = fs::read_dir(test_dir).expect("Failed to read test directory");
let mut test_files = Vec::new();
let mut failures = Vec::new();
for entry in entries {
let entry = entry.expect("Failed to read directory entry");
let path = entry.path();
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
if filename.starts_with("test-tokens-") && filename.ends_with(".txt") {
test_files.push(path);
}
}
}
test_files.sort();
println!("Found {} tokens test files", test_files.len());
for test_file in &test_files {
match run_single_tokens_test(test_file) {
Ok(()) => {
println!("✓ {:?}", test_file.file_name().unwrap());
}
Err(e) => {
println!("✗ {:?}: {}", test_file.file_name().unwrap(), e);
failures.push(format!("{}: {}", test_file.display(), e));
}
}
}
if !failures.is_empty() {
println!("\n{} test(s) failed:", failures.len());
for failure in &failures[..std::cmp::min(5, failures.len())] {
println!(" {}", failure);
}
if failures.len() > 5 {
println!(" ... and {} more", failures.len() - 5);
}
panic!("{} tokens test(s) failed", failures.len());
}
println!("All {} tokens tests passed!", test_files.len());
}
#[test]
fn test_single_tokens_example() {
let input = "SELECT x'1234';";
let expected = "E SELECT\n1 x'1234'\n; ;";
let actual = run_sqli_tokenization(input.as_bytes());
println!("Input: {}", input);
println!("Expected:\n{}", expected);
println!("Actual:\n{}", actual);
assert_eq!(actual, expected, "Simple tokens tokenization test failed");
}
#[test]
fn test_backquote_variable_debug() {
let input = "SELECT @`foo``bar`;";
println!("Input: {}", input);
let input_bytes = input.as_bytes();
let flags = SqliFlags::FLAG_SQL_ANSI;
let mut tokenizer = SqliTokenizer::new(input_bytes, flags);
println!("Tokens:");
while let Some(token) = tokenizer.next_token() {
println!(" Type: {:?} ({}), Value: {:?}, Pos: {}, Len: {}",
token.token_type, token_type_to_char(token.token_type),
token.value_as_str(), token.pos, token.len);
}
let expected = "E SELECT\nv @`foo``bar`\n; ;";
let actual = run_sqli_tokenization(input.as_bytes());
println!("Expected:\n{}", expected);
println!("Actual:\n{}", actual);
}
#[test]
fn test_b_string_debug() {
let input = "SELECT b'";
println!("Input: {}", input);
let input_bytes = input.as_bytes();
let flags = SqliFlags::FLAG_SQL_ANSI;
let mut tokenizer = SqliTokenizer::new(input_bytes, flags);
println!("Tokens:");
let mut token_count = 0;
while let Some(token) = tokenizer.next_token() {
token_count += 1;
println!(" {}: Type: {:?} ({}), Value: {:?}, Pos: {}, Len: {}, str_open: {}, str_close: {}",
token_count, token.token_type, token_type_to_char(token.token_type),
token.value_as_str(), token.pos, token.len, token.str_open, token.str_close);
}
println!(" Total tokens: {}, Input length: {}", token_count, input.len());
let expected = "E SELECT\nn b\ns '";
let actual = run_sqli_tokenization(input.as_bytes());
println!("Expected:\n{}", expected);
println!("Actual:\n{}", actual);
}
#[test]
fn test_utf8_fix_verification() {
let input = "SELECT テスト;";
println!("\n=== UTF-8 Fix Verification ===");
println!("Testing: {}", input);
println!("UTF-8 bytes: {:?}", input.as_bytes());
let expected = "E SELECT\nn テスト\n; ;";
let actual = run_sqli_tokenization(input.as_bytes());
println!("Expected:\n{}", expected);
println!("Actual:\n{}", actual);
assert_eq!(actual, expected, "UTF-8 tokenization should match C behavior");
}
}