use proptest::prelude::*;
use waf_core::{Bytes, LimitsConfig, ParsedBody};
use waf_normalizer::body::parse_body;
use waf_normalizer::url::parse_query;
fn parse_form(body: &[u8], limits: &LimitsConfig) -> Result<Vec<(String, String)>, ()> {
match parse_body(
Some("application/x-www-form-urlencoded"),
&Bytes::copy_from_slice(body),
limits,
) {
Ok(ParsedBody::FormUrlEncoded(p)) => Ok(p),
Ok(other) => unreachable!("form content-type must yield FormUrlEncoded, got {other:?}"),
Err(_) => Err(()),
}
}
fn limits(max_params: usize) -> LimitsConfig {
let mut l = LimitsConfig::default();
l.max_params = max_params;
l
}
proptest! {
#[test]
fn prop_form_valid_utf8_equals_query(s in "(&|=|%26|%3D|%25|[a-z0-9+]){0,60}") {
let lim = limits(8);
let form = parse_form(s.as_bytes(), &lim);
let query = parse_query(&s, &lim).map(|(p, _, _)| p).map_err(|_| ());
prop_assert_eq!(form, query);
}
}
proptest! {
#[test]
fn prop_form_arbitrary_bytes_all_or_nothing(body in prop::collection::vec(any::<u8>(), 0..256)) {
let lim = limits(8);
let res = parse_form(&body, &lim);
if let Ok(params) = &res {
prop_assert!(params.len() <= lim.max_params);
}
if std::str::from_utf8(&body).is_err() {
prop_assert_eq!(res, Ok(Vec::new()));
}
}
}
#[test]
fn invalid_byte_drops_whole_body_not_partial() {
let lim = LimitsConfig::default();
assert_eq!(parse_form(b"a=b\xFF", &lim), Ok(Vec::new()));
assert_eq!(parse_form(b"\xFFa=b", &lim), Ok(Vec::new()));
assert_eq!(parse_form(b"a=b&c=d\xFF", &lim), Ok(Vec::new()));
let r = parse_form(b"a=b\xFF", &lim).unwrap();
assert!(r.is_empty(), "strict→empty, not lossy replacement: {r:?}");
}
#[test]
fn valid_utf8_form_parses_like_query() {
let lim = LimitsConfig::default();
assert_eq!(
parse_form(b"a=b&c=d", &lim),
Ok(vec![("a".to_string(), "b".to_string()), ("c".to_string(), "d".to_string())])
);
assert_eq!(parse_form(b"a=x+y", &lim), Ok(vec![("a".to_string(), "x y".to_string())]));
}