use wafrift_encoding::contextual::{encode_in_context, escape_for_context, validate_in_context};
use wafrift_encoding::Strategy;
use wafrift_types::injection_context::InjectionContext;
#[test]
fn json_string_basic() {
let out = encode_in_context(b"hello", Strategy::CaseAlternation, InjectionContext::JsonString).unwrap();
assert!(!out.contains('"'));
assert!(!out.contains('\\'));
}
#[test]
fn json_string_escapes_quotes() {
let out = encode_in_context(b"\" OR 1=1", Strategy::CaseAlternation, InjectionContext::JsonString).unwrap();
assert!(out.contains("\\\""));
}
#[test]
fn json_string_escapes_backslash() {
let out = encode_in_context(b"a\\b", Strategy::CaseAlternation, InjectionContext::JsonString).unwrap();
assert!(out.contains("\\\\"));
}
#[test]
fn json_string_escapes_control_chars() {
let out = encode_in_context(b"a\nb", Strategy::CaseAlternation, InjectionContext::JsonString).unwrap();
assert!(out.contains("\\n"));
}
#[test]
fn json_string_unicode_encode_no_collision() {
let out = encode_in_context(b"a", Strategy::UnicodeEncode, InjectionContext::JsonString).unwrap();
assert!(out.contains("\\\\u0061"));
}
#[test]
fn json_string_null_byte_escaped() {
let out = encode_in_context(b"a\x00b", Strategy::CaseAlternation, InjectionContext::JsonString).unwrap();
assert!(out.contains("\\u0000"));
}
#[test]
fn json_string_with_url_encode_strategy() {
let out = encode_in_context(b"a b", Strategy::UrlEncode, InjectionContext::JsonString).unwrap();
assert!(out.contains("%20"));
assert!(!out.contains('"')); }
#[test]
fn json_number_valid() {
let out = encode_in_context(b"123", Strategy::CaseAlternation, InjectionContext::JsonNumber).unwrap();
assert_eq!(out, "123");
}
#[test]
fn json_number_with_decimal() {
let out = encode_in_context(b"-1.5e+10", Strategy::CaseAlternation, InjectionContext::JsonNumber).unwrap();
assert_eq!(out, "-1.5E+10");
}
#[test]
fn json_number_invalid_rejected() {
let err = encode_in_context(b"abc", Strategy::CaseAlternation, InjectionContext::JsonNumber).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn json_number_url_encode_rejected() {
let err = encode_in_context(b"1 23", Strategy::UrlEncode, InjectionContext::JsonNumber).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn xml_attribute_escapes_quotes() {
let out = encode_in_context(b"\"x\"", Strategy::CaseAlternation, InjectionContext::XmlAttribute).unwrap();
assert!(out.contains("""));
}
#[test]
fn xml_attribute_escapes_ampersand() {
let out = encode_in_context(b"a&b", Strategy::CaseAlternation, InjectionContext::XmlAttribute).unwrap();
assert!(out.contains("&"));
}
#[test]
fn xml_attribute_escapes_lt_gt() {
let out = encode_in_context(b"<script>", Strategy::CaseAlternation, InjectionContext::XmlAttribute).unwrap();
assert!(out.contains("<"));
assert!(out.contains(">"));
}
#[test]
fn xml_attribute_null_byte_rejected() {
let err = encode_in_context(b"a\x00b", Strategy::CaseAlternation, InjectionContext::XmlAttribute).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn xml_cdata_passes_through() {
let out = encode_in_context(b"hello world", Strategy::CaseAlternation, InjectionContext::XmlCdata).unwrap();
assert!(out.contains("HeLlO"));
}
#[test]
fn xml_cdata_terminator_rejected() {
let err = encode_in_context(b"]]>", Strategy::CaseAlternation, InjectionContext::XmlCdata).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn xml_text_escapes_special_chars() {
let out = encode_in_context(b"a < b & c > d", Strategy::CaseAlternation, InjectionContext::XmlText).unwrap();
assert!(out.contains("<"));
assert!(out.contains("&"));
assert!(out.contains(">"));
}
#[test]
fn html_attribute_escapes_both_quotes() {
let out = encode_in_context(b"'\"x", Strategy::CaseAlternation, InjectionContext::HtmlAttribute).unwrap();
assert!(out.contains("'") || out.contains("'")); assert!(out.contains(""") || out.contains('"'));
}
#[test]
fn html_attribute_escapes_amp_and_lt() {
let out = encode_in_context(b"a&b<c", Strategy::CaseAlternation, InjectionContext::HtmlAttribute).unwrap();
assert!(out.contains("&"));
assert!(out.contains("<"));
}
#[test]
fn html_text_escapes_amp_and_lt() {
let out = encode_in_context(b"a&b<c", Strategy::CaseAlternation, InjectionContext::HtmlText).unwrap();
assert!(out.contains("&"));
assert!(out.contains("<"));
}
#[test]
fn url_query_case_alternation_no_raw_space() {
let out = encode_in_context(b"a b", Strategy::CaseAlternation, InjectionContext::UrlQuery).unwrap();
assert!(!out.contains(' '), "raw space found in URL query: {}", out);
}
#[test]
fn url_query_url_encode_no_double_encoding() {
let out = encode_in_context(b"a b", Strategy::UrlEncode, InjectionContext::UrlQuery).unwrap();
assert!(out.contains("%2520"));
}
#[test]
fn url_query_unicode_escape_valid() {
let out = encode_in_context("ä".as_bytes(), Strategy::UnicodeEncode, InjectionContext::UrlQuery).unwrap();
assert!(out.to_ascii_lowercase().contains("%5cu00e4"));
}
#[test]
fn url_path_slash_not_encoded() {
let out = encode_in_context(b"/api/v1", Strategy::CaseAlternation, InjectionContext::UrlPath).unwrap();
assert!(out.contains("/"));
}
#[test]
fn url_path_space_encoded() {
let out = encode_in_context(b"a b", Strategy::CaseAlternation, InjectionContext::UrlPath).unwrap();
assert!(!out.contains(' '), "raw space found in URL path: {}", out);
}
#[test]
fn header_value_passes_through_clean() {
let out = encode_in_context(b"Bearer token123", Strategy::CaseAlternation, InjectionContext::HeaderValue).unwrap();
assert!(out.contains("BeArEr"));
}
#[test]
fn header_value_cr_rejected() {
let err = encode_in_context(b"a\rb", Strategy::CaseAlternation, InjectionContext::HeaderValue).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn header_value_lf_rejected() {
let err = encode_in_context(b"a\nb", Strategy::CaseAlternation, InjectionContext::HeaderValue).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn header_value_null_rejected() {
let err = encode_in_context(b"a\x00b", Strategy::CaseAlternation, InjectionContext::HeaderValue).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn cookie_value_escapes_semicolon() {
let out = encode_in_context(b"a;b", Strategy::CaseAlternation, InjectionContext::CookieValue).unwrap();
assert!(out.contains("%3B"));
}
#[test]
fn cookie_value_escapes_equals() {
let out = encode_in_context(b"a=b", Strategy::CaseAlternation, InjectionContext::CookieValue).unwrap();
assert!(out.contains("%3D"));
}
#[test]
fn cookie_value_escapes_null() {
let out = encode_in_context(b"a\x00b", Strategy::CaseAlternation, InjectionContext::CookieValue).unwrap();
assert!(out.contains("%00"));
}
#[test]
fn multipart_field_passes_through() {
let out = encode_in_context(b"hello", Strategy::CaseAlternation, InjectionContext::MultipartField).unwrap();
assert!(out.contains("HeLlO"));
}
#[test]
fn multipart_field_cr_rejected() {
let err = encode_in_context(b"a\rb", Strategy::CaseAlternation, InjectionContext::MultipartField).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn multipart_field_lf_rejected() {
let err = encode_in_context(b"a\nb", Strategy::CaseAlternation, InjectionContext::MultipartField).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn multipart_filename_passes_through() {
let out = encode_in_context(b"file.txt", Strategy::CaseAlternation, InjectionContext::MultipartFileName).unwrap();
assert!(out.contains("FiLe"));
}
#[test]
fn multipart_filename_quote_rejected() {
let err = encode_in_context(b"\"evil\".txt", Strategy::CaseAlternation, InjectionContext::MultipartFileName).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn plain_body_no_structural_escaping() {
let out = encode_in_context(b"<script>alert(1)</script>", Strategy::CaseAlternation, InjectionContext::PlainBody).unwrap();
assert!(!out.is_empty());
}
#[test]
fn plain_body_with_html_entity_strategy() {
let out = encode_in_context(b"<script>alert(1)</script>", Strategy::HtmlEntityEncode, InjectionContext::PlainBody).unwrap();
assert!(out.contains("<") || out.contains(">") || out == "<script>alert(1)</script>");
}
#[test]
fn none_context_delegates_to_plain() {
}
#[test]
fn validate_detects_invalid_json_string() {
let err = validate_in_context("unescaped\"quote", InjectionContext::JsonString).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn validate_accepts_valid_json_string() {
assert!(validate_in_context("safe text", InjectionContext::JsonString).is_ok());
assert!(validate_in_context("escaped\\\"quote", InjectionContext::JsonString).is_ok());
}
#[test]
fn validate_detects_invalid_xml_attribute() {
let err = validate_in_context("a\"b", InjectionContext::XmlAttribute).unwrap_err();
assert!(err.to_string().contains("incompatible"));
}
#[test]
fn validate_accepts_valid_xml_attribute() {
assert!(validate_in_context("safe&text", InjectionContext::XmlAttribute).is_ok());
}
#[test]
fn escape_for_context_json_string() {
let out = escape_for_context("\"x\"", InjectionContext::JsonString).unwrap();
assert!(out.contains("\\\""));
}
#[test]
fn escape_for_context_cookie_value() {
let out = escape_for_context("a;b=c", InjectionContext::CookieValue).unwrap();
assert!(out.contains("%3B"));
assert!(out.contains("%3D"));
}
#[test]
fn json_string_max_size_enforced() {
let big = vec![b'a'; 4 * 1024 * 1024 + 1];
let err = encode_in_context(&big, Strategy::CaseAlternation, InjectionContext::JsonString).unwrap_err();
assert!(err.to_string().contains("too large"));
}
#[test]
fn cookie_value_max_size_enforced() {
let big = vec![b'a'; 4 * 1024 + 1];
let err = encode_in_context(&big, Strategy::CaseAlternation, InjectionContext::CookieValue).unwrap_err();
assert!(err.to_string().contains("too large"));
}
#[test]
fn multipart_filename_max_size_enforced() {
let big = vec![b'a'; 257];
let err = encode_in_context(&big, Strategy::CaseAlternation, InjectionContext::MultipartFileName).unwrap_err();
assert!(err.to_string().contains("too large"));
}
#[test]
fn exact_max_size_allowed() {
let exact = vec![b'a'; 4 * 1024 * 1024];
assert!(encode_in_context(&exact, Strategy::CaseAlternation, InjectionContext::JsonString).is_ok());
}
#[test]
fn all_strategies_work_with_plain_body() {
let payload = b"' OR 1=1 --";
for strategy in wafrift_encoding::all_strategies() {
let result = encode_in_context(payload, strategy, InjectionContext::PlainBody);
assert!(result.is_ok(), "strategy {:?} failed on PlainBody: {:?}", strategy, result);
}
}