use proptest::prelude::*;
use waf_core::LimitsConfig;
use waf_normalizer::url::{canonicalize_value, parse_query};
fn oracle(query: &str, max_params: usize) -> Result<Vec<(String, String)>, ()> {
let mut out = Vec::new();
for pair in query.split('&') {
if pair.is_empty() {
continue;
}
if out.len() >= max_params {
return Err(());
}
let (k, v) = match pair.find('=') {
Some(p) => (&pair[..p], &pair[p + 1..]),
None => (pair, ""),
};
out.push((canonicalize_value(k, true).0, canonicalize_value(v, true).0));
}
Ok(out)
}
fn limits(max_params: usize) -> LimitsConfig {
let mut l = LimitsConfig::default();
l.max_params = max_params;
l
}
proptest! {
#[test]
fn prop_query_matches_split_then_canonicalize(
s in "(&|=|%26|%3D|%25|[a-z0-9]){0,60}",
) {
let lim = limits(8);
let ours = parse_query(&s, &lim).map(|(p, _, _)| p).map_err(|_| ());
prop_assert_eq!(ours, oracle(&s, lim.max_params));
}
}
proptest! {
#[test]
fn prop_query_non_panic_and_bounded(s in ".{0,128}") {
let lim = limits(8);
if let Ok((params, _, _)) = parse_query(&s, &lim) {
prop_assert!(params.len() <= lim.max_params);
}
}
}
#[test]
fn encoded_delimiters_stay_in_value() {
let l = LimitsConfig::default();
let (p, _, _) = parse_query("a=b%26c", &l).unwrap();
assert_eq!(p, vec![("a".to_string(), "b&c".to_string())]);
let (p, _, _) = parse_query("a=b%3Dc", &l).unwrap();
assert_eq!(p, vec![("a".to_string(), "b=c".to_string())]);
let (p, _, _) = parse_query("a=b=c", &l).unwrap();
assert_eq!(p, vec![("a".to_string(), "b=c".to_string())]);
}
#[test]
fn degenerate_separators_do_not_inflate_count() {
let l = LimitsConfig::default();
assert!(parse_query("&&&&", &l).unwrap().0.is_empty());
assert_eq!(parse_query("=", &l).unwrap().0, vec![("".to_string(), "".to_string())]);
assert_eq!(parse_query("k", &l).unwrap().0, vec![("k".to_string(), "".to_string())]);
}
#[test]
fn over_limit_is_rejected_deterministically() {
let lim = limits(3);
assert!(parse_query("a&b&c", &lim).is_ok()); assert!(parse_query("a&b&c&d", &lim).is_err()); }