use proptest::prelude::*;
use waf_core::{Bytes, LimitsConfig, ParsedBody};
use waf_normalizer::body::parse_body;
fn check_multipart(boundary: &str, body: Vec<u8>) {
let ct = format!("multipart/form-data; boundary={boundary}");
let len = body.len();
let parsed = parse_body(Some(&ct), &Bytes::from(body), &LimitsConfig::default());
if let Ok(ParsedBody::Multipart(fields)) = parsed {
assert!(
fields.len() <= len,
"unbounded parts: {} fields from {len} bytes",
fields.len()
);
}
}
proptest! {
#[test]
fn prop_multipart_arbitrary_never_panics(
boundary in "[a-zA-Z0-9]{0,8}",
body in prop::collection::vec(any::<u8>(), 0..512),
) {
check_multipart(&boundary, body);
}
}
#[derive(Debug, Clone)]
enum Tok {
Delim, EndDelim, Crlf,
Dashes, Disposition, ContentType, Junk(Vec<u8>), }
fn tok() -> impl Strategy<Value = Tok> {
prop_oneof![
Just(Tok::Delim),
Just(Tok::EndDelim),
Just(Tok::Crlf),
Just(Tok::Dashes),
Just(Tok::Disposition),
Just(Tok::ContentType),
prop::collection::vec(any::<u8>(), 0..16).prop_map(Tok::Junk),
]
}
fn render(boundary: &str, toks: &[Tok]) -> Vec<u8> {
let mut out = Vec::new();
for t in toks {
match t {
Tok::Delim => {
out.extend_from_slice(b"--");
out.extend_from_slice(boundary.as_bytes());
}
Tok::EndDelim => {
out.extend_from_slice(b"--");
out.extend_from_slice(boundary.as_bytes());
out.extend_from_slice(b"--");
}
Tok::Crlf => out.extend_from_slice(b"\r\n"),
Tok::Dashes => out.extend_from_slice(b"--"),
Tok::Disposition => {
out.extend_from_slice(b"content-disposition: form-data; name=\"f\"")
}
Tok::ContentType => out.extend_from_slice(b"content-type: text/plain"),
Tok::Junk(b) => out.extend_from_slice(b),
}
}
out
}
proptest! {
#[test]
fn prop_multipart_structured_never_panics(
boundary in "[a-zA-Z0-9]{0,6}",
toks in prop::collection::vec(tok(), 0..40),
) {
check_multipart(&boundary, render(&boundary, &toks));
}
}