#![allow(dead_code)]
use form_net::{Boundary, Event, FormError, Limits, MultipartParser, PartKind, Policy};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Record {
pub name: String,
pub kind: PartKind,
pub filename: Option<Vec<u8>>,
pub data: Vec<u8>,
}
pub fn drive(
boundary: &[u8],
chunks: &[&[u8]],
limits: &Limits,
policy: &Policy,
) -> Result<Vec<Record>, FormError> {
let mut parser = MultipartParser::new(Boundary::new(boundary)?, limits, policy)?;
let mut out = Vec::new();
let mut current: Option<Record> = None;
let mut done = false;
for (i, chunk) in chunks.iter().enumerate() {
let last = i + 1 == chunks.len();
let mut rest: &[u8] = chunk;
loop {
let step = parser.parse(rest, last)?;
rest = &rest[step.consumed..];
match step.event {
Event::PartStart(head) => {
assert!(current.is_none(), "part started inside a part");
current = Some(Record {
name: head.name().to_owned(),
kind: head.kind(),
filename: head.filename().map(|f| f.raw().to_vec()),
data: Vec::new(),
});
}
Event::Data(bytes) => {
assert!(!bytes.is_empty(), "empty Data event");
current
.as_mut()
.expect("data outside a part")
.data
.extend_from_slice(bytes);
}
Event::PartEnd(summary) => {
let rec = current.take().expect("end outside a part");
assert_eq!(summary.bytes, rec.data.len() as u64);
out.push(rec);
}
Event::End => {
done = true;
if !rest.is_empty() {
let step = parser.parse(rest, last)?;
assert!(matches!(step.event, Event::End));
assert_eq!(step.consumed, rest.len());
}
break;
}
Event::NeedInput => {
assert!(rest.is_empty(), "NeedInput with bytes left");
break;
}
_ => unreachable!(),
}
}
if done {
for (j, more) in chunks.iter().enumerate().skip(i + 1) {
let step = parser.parse(more, j + 1 == chunks.len())?;
assert!(matches!(step.event, Event::End));
}
break;
}
}
assert!(done || chunks.is_empty(), "parser finished without End");
Ok(out)
}
pub fn whole(boundary: &[u8], body: &[u8]) -> Result<Vec<Record>, FormError> {
drive(boundary, &[body], &Limits::DEFAULT, &Policy::DEFAULT)
}
pub fn split_at(
boundary: &[u8],
body: &[u8],
at: usize,
limits: &Limits,
policy: &Policy,
) -> Result<Vec<Record>, FormError> {
let (a, b) = body.split_at(at);
drive(boundary, &[a, b], limits, policy)
}
pub fn bytewise(
boundary: &[u8],
body: &[u8],
limits: &Limits,
policy: &Policy,
) -> Result<Vec<Record>, FormError> {
let chunks: Vec<&[u8]> = body.chunks(1).collect();
drive(boundary, &chunks, limits, policy)
}
pub fn assert_split_invariant(boundary: &[u8], body: &[u8], limits: &Limits, policy: &Policy) {
let expected = drive(boundary, &[body], limits, policy);
for at in 0..=body.len() {
let got = split_at(boundary, body, at, limits, policy);
assert_eq!(got, expected, "split at {at}");
}
assert_eq!(
bytewise(boundary, body, limits, policy),
expected,
"bytewise"
);
for size in [2, 3, 7, 64] {
let chunks: Vec<&[u8]> = body.chunks(size).collect();
assert_eq!(
drive(boundary, &chunks, limits, policy),
expected,
"chunks of {size}"
);
}
}
pub fn body(boundary: &str, parts: &[(&str, &[u8])]) -> Vec<u8> {
let mut out = Vec::new();
for (headers, data) in parts {
out.extend_from_slice(format!("--{boundary}\r\n{headers}\r\n\r\n").as_bytes());
out.extend_from_slice(data);
out.extend_from_slice(b"\r\n");
}
out.extend_from_slice(format!("--{boundary}--\r\n").as_bytes());
out
}
pub fn reference(boundary: &[u8], body: &[u8]) -> Option<Vec<Record>> {
let mut first = b"--".to_vec();
first.extend_from_slice(boundary);
let mut delim = b"\r\n--".to_vec();
delim.extend_from_slice(boundary);
let find = |hay: &[u8], needle: &[u8], from: usize| -> Option<usize> {
hay.get(from..)?
.windows(needle.len())
.position(|w| w == needle)
.map(|p| p + from)
};
let mut pos = if body.starts_with(&first) {
first.len()
} else {
find(body, &delim, 0)? + delim.len()
};
let mut out = Vec::new();
loop {
if body.get(pos..pos + 2)? == b"--" {
return Some(out);
}
if body.get(pos..pos + 2)? != b"\r\n" {
return None;
}
pos += 2;
let head_end = find(body, b"\r\n\r\n", pos)?;
let head = std::str::from_utf8(&body[pos..head_end]).ok()?;
let data_start = head_end + 4;
let data_end = find(body, &delim, data_start)?;
let line = head.strip_prefix("Content-Disposition: form-data; ")?;
let mut name = None;
let mut filename = None;
for param in line.split("; ") {
let (k, v) = param.split_once('=')?;
let v = v.strip_prefix('"')?.strip_suffix('"')?;
match k {
"name" => {
name = Some(
v.replace("%22", "\"")
.replace("%0D", "\r")
.replace("%0A", "\n"),
)
}
"filename" => filename = Some(v.as_bytes().to_vec()),
_ => return None,
}
}
out.push(Record {
name: name?,
kind: if filename.is_some() {
PartKind::File
} else {
PartKind::Field
},
filename,
data: body[data_start..data_end].to_vec(),
});
pos = data_end + delim.len();
}
}