#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::expect_used)]
#[allow(clippy::indexing_slicing)]
#[allow(clippy::disallowed_methods)]
#[allow(clippy::panic)]
mod tests {
use crate::detect_xss;
#[test]
fn test_final_xss_api() {
let result = detect_xss(b"<script>alert(1)</script>");
assert!(result.is_injection());
println!("XSS Detection Result: {:?}", result);
let safe_result = detect_xss(b"Hello world");
assert!(!safe_result.is_injection());
println!("Safe Result: {:?}", safe_result);
}
#[test]
fn test_crash_input() {
use crate::xss::{Html5State, Html5Flags, TokenType, XssDetector, AttributeType};
let input = &[13u8, 60, 33, 255, 62, 60, 96, 60];
println!("Testing crash input: {:?}", input);
println!("As string (lossy): {:?}", String::from_utf8_lossy(input));
let is_xss = debug_is_xss_with_trace(input, Html5Flags::DataState);
println!("Debug function result: {}", is_xss);
let real_result = detect_xss(input);
println!("Real detect_xss result: {:?} (is_injection: {})", real_result, real_result.is_injection());
println!("\n=== Testing each context individually ===");
let contexts = [
("DataState", Html5Flags::DataState),
("ValueNoQuote", Html5Flags::ValueNoQuote),
("ValueSingleQuote", Html5Flags::ValueSingleQuote),
("ValueDoubleQuote", Html5Flags::ValueDoubleQuote),
("ValueBackQuote", Html5Flags::ValueBackQuote),
];
for (name, context) in &contexts {
let is_xss_in_context = XssDetector::is_xss(input, *context);
println!("{}: {}", name, is_xss_in_context);
if is_xss_in_context {
println!(" *** {} triggers false positive ***", name);
}
}
}
fn debug_is_xss_with_trace(input: &[u8], flags: crate::xss::Html5Flags) -> bool {
use crate::xss::{Html5State, TokenType, XssDetector, AttributeType};
println!("=== DETAILED XSS TRACE FOR DATA_STATE ===");
let mut html5 = Html5State::new(input, flags);
let mut attr = AttributeType::None;
let mut token_count = 0;
while html5.next() && token_count < 20 {
token_count += 1;
println!("Token {}: {:?}", token_count, html5.token_type);
if html5.token_len > 0 && html5.token_len <= html5.token_start.len() {
let token_slice = &html5.token_start[..html5.token_len];
println!(" Content: {:?} (raw: {:?})", String::from_utf8_lossy(token_slice), token_slice);
}
if html5.token_type != TokenType::AttrValue {
attr = AttributeType::None;
}
match html5.token_type {
TokenType::Doctype => {
println!(" -> DOCTYPE detected - returning XSS=true");
return true;
}
TokenType::TagNameOpen => {
if html5.token_len > 0 && html5.token_len <= html5.token_start.len() {
let tag_slice = &html5.token_start[..html5.token_len];
println!(" -> Checking tag name: {:?}", String::from_utf8_lossy(tag_slice));
let is_blacklisted = tag_slice.len() >= 3 && {
let blacklist = [
"APPLET", "BASE", "COMMENT", "EMBED", "FRAME", "FRAMESET",
"HANDLER", "IFRAME", "IMPORT", "ISINDEX", "LINK", "LISTENER",
"META", "NOSCRIPT", "OBJECT", "SCRIPT", "STYLE", "VMLFRAME", "XML", "XSS"
];
let tag_upper = tag_slice.to_ascii_uppercase();
let tag_str = String::from_utf8_lossy(&tag_upper);
blacklist.iter().any(|&black_tag| tag_str == black_tag) ||
tag_slice.len() >= 3 && (
tag_slice[..3].eq_ignore_ascii_case(b"svg") ||
tag_slice[..3].eq_ignore_ascii_case(b"xsl")
)
};
if is_blacklisted {
println!(" -> Blacklisted tag detected - returning XSS=true");
return true;
}
println!(" -> Tag is safe");
}
}
TokenType::AttrName => {
if html5.token_len > 0 && html5.token_len <= html5.token_start.len() {
let attr_slice = &html5.token_start[..html5.token_len];
println!(" -> Checking attribute: {:?}", String::from_utf8_lossy(attr_slice));
attr = AttributeType::None;
println!(" -> Attribute type: None (simplified)");
}
}
TokenType::AttrValue => {
println!(" -> Processing attribute value with attr={:?}", attr);
match attr {
AttributeType::None => {
println!(" -> Safe attribute value");
}
AttributeType::Black => {
println!(" -> Blacklisted attribute value - returning XSS=true");
return true;
}
AttributeType::Style => {
println!(" -> Style attribute - returning XSS=true");
return true;
}
_ => {
println!(" -> Other attribute processing");
}
}
attr = AttributeType::None;
}
TokenType::TagComment => {
if html5.token_len > 0 && html5.token_len <= html5.token_start.len() {
let comment_slice = &html5.token_start[..html5.token_len];
println!(" -> Checking comment: {:?}", String::from_utf8_lossy(comment_slice));
if comment_slice.contains(&b'`') {
println!(" -> Backtick in comment - returning XSS=true");
return true;
}
let is_dangerous = comment_slice.len() > 3 && (
(comment_slice.len() >= 3
&& comment_slice[0] == b'['
&& (comment_slice[1] == b'i' || comment_slice[1] == b'I')
&& (comment_slice[2] == b'f' || comment_slice[2] == b'F')) ||
(comment_slice.len() >= 3
&& (comment_slice[0] == b'x' || comment_slice[0] == b'X')
&& (comment_slice[1] == b'm' || comment_slice[1] == b'M')
&& (comment_slice[2] == b'l' || comment_slice[2] == b'L'))
) || comment_slice.len() > 5 && (
comment_slice.len() >= 6 && (
comment_slice[..6].eq_ignore_ascii_case(b"IMPORT") ||
comment_slice[..6].eq_ignore_ascii_case(b"ENTITY")
)
);
if is_dangerous {
println!(" -> Dangerous comment detected - returning XSS=true");
return true;
}
println!(" -> Comment is safe");
}
}
_ => {
if html5.token_len > 0 && html5.token_len <= html5.token_start.len() {
let token_slice = &html5.token_start[..html5.token_len];
if token_slice.contains(&b'`') {
println!(" -> *** BACKTICK FOUND IN NON-COMMENT TOKEN! ***");
println!(" -> Token type: {:?}, Content: {:?}", html5.token_type, String::from_utf8_lossy(token_slice));
}
}
println!(" -> Other token type: {:?}", html5.token_type);
}
}
}
if token_count >= 20 {
println!(" -> Stopped after 20 tokens (possible infinite loop)");
}
println!(" -> End of input reached - returning XSS=false");
false
}
}