use core::fmt;
use crate::error::{FormError, Limit, Location};
use crate::limits::Limits;
use crate::urlencoded::raw::{Budget, RawPair};
pub struct UrlencodedParser<B: AsMut<[u8]> + AsRef<[u8]>> {
scratch: B,
held: usize,
held_at: u64,
release: bool,
seen: u64,
budget: Budget,
poisoned: bool,
ended: bool,
}
impl<B: AsMut<[u8]> + AsRef<[u8]>> fmt::Debug for UrlencodedParser<B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UrlencodedParser")
.field("bytes_seen", &self.seen)
.field("held", &self.held)
.field("ended", &self.ended)
.finish_non_exhaustive()
}
}
#[non_exhaustive]
#[derive(Debug)]
pub struct UrlencodedStep<'p> {
pub consumed: usize,
pub event: UrlencodedEvent<'p>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
pub enum UrlencodedEvent<'p> {
Pair(RawPair<'p>),
NeedInput,
End,
}
enum Found {
Input { start: usize, end: usize, at: u64 },
Scratch { len: usize, at: u64 },
NeedInput,
End,
}
impl<B: AsMut<[u8]> + AsRef<[u8]>> UrlencodedParser<B> {
pub fn with_buffer(limits: &Limits, scratch: B) -> Result<Self, FormError> {
limits.validate()?;
let needed = limits.urlencoded_buffer_len();
if scratch.as_ref().len() < needed {
return Err(FormError::BufferTooSmall { needed });
}
Ok(UrlencodedParser {
scratch,
held: 0,
held_at: 0,
release: false,
seen: 0,
budget: Budget::new(limits),
poisoned: false,
ended: false,
})
}
pub fn parse<'p>(
&'p mut self,
input: &'p [u8],
eof: bool,
) -> Result<UrlencodedStep<'p>, FormError> {
if self.poisoned {
return Err(FormError::Poisoned);
}
if self.release {
self.release = false;
self.clear_held();
}
let (consumed, found) = match self.step(input, eof) {
Ok(r) => r,
Err(e) => {
self.poisoned = true;
self.clear_held();
return Err(e);
}
};
let event = match found {
Found::Input { start, end, at } => {
UrlencodedEvent::Pair(RawPair::split(input.get(start..end).unwrap_or(&[]), at))
}
Found::Scratch { len, at } => {
self.release = true;
let seq = self.scratch.as_ref().get(..len).unwrap_or(&[]);
UrlencodedEvent::Pair(RawPair::split(seq, at))
}
Found::NeedInput => UrlencodedEvent::NeedInput,
Found::End => UrlencodedEvent::End,
};
Ok(UrlencodedStep { consumed, event })
}
pub fn reset(&mut self) {
crate::wipe::wipe(self.scratch.as_mut());
self.held = 0;
self.held_at = 0;
self.release = false;
self.seen = 0;
self.budget.reset();
self.poisoned = false;
self.ended = false;
}
#[must_use]
pub const fn bytes_seen(&self) -> u64 {
self.seen
}
fn clear_held(&mut self) {
if let Some(held) = self.scratch.as_mut().get_mut(..self.held) {
crate::wipe::wipe(held);
}
self.held = 0;
}
fn step(&mut self, input: &[u8], eof: bool) -> Result<(usize, Found), FormError> {
if self.ended {
return Ok((0, Found::End));
}
let body = self.budget.limits.body();
let mut pos = 0usize;
loop {
let rest = input.get(pos..).unwrap_or(&[]);
let allowed = usize::try_from(body.saturating_sub(self.seen)).unwrap_or(usize::MAX);
let window = rest.get(..allowed.min(rest.len())).unwrap_or(&[]);
let over = rest.len() > window.len();
let amp = crate::scan::find_any(window, 0, *b"&&&&").min(window.len());
let terminated = amp < window.len();
let piece = window.get(..amp).unwrap_or(&[]);
if !terminated && (!eof || over) {
self.check_partial(piece)?;
if over {
return Err(FormError::limit(Limit::Body, Location::at(body)));
}
self.hold(piece);
pos = pos.saturating_add(piece.len());
self.seen = self.seen.saturating_add(piece.len() as u64);
return Ok((pos, Found::NeedInput));
}
let start = pos;
let at = self.seen;
let seq_len = self.held.saturating_add(piece.len());
pos = pos.saturating_add(piece.len());
self.seen = self.seen.saturating_add(piece.len() as u64);
if terminated {
pos = pos.saturating_add(1);
self.seen = self.seen.saturating_add(1);
}
if seq_len == 0 {
if terminated {
continue;
}
self.ended = true;
return Ok((pos, Found::End));
}
if self.held == 0 {
self.budget.admit(&RawPair::split(piece, at))?;
return Ok((
pos,
Found::Input {
start,
end: start.saturating_add(piece.len()),
at,
},
));
}
self.check_partial(piece)?;
self.hold(piece);
let at = self.held_at;
let len = self.held;
let seq = self.scratch.as_ref().get(..len).unwrap_or(&[]);
self.budget.admit(&RawPair::split(seq, at))?;
return Ok((pos, Found::Scratch { len, at }));
}
}
fn check_partial(&self, more: &[u8]) -> Result<(), FormError> {
let held = self.scratch.as_ref().get(..self.held).unwrap_or(&[]);
let at = if self.held == 0 {
self.seen
} else {
self.held_at
};
let (name_len, has_equals, value_len) = match held.iter().position(|&b| b == b'=') {
Some(e) => (
e,
true,
held.len()
.saturating_sub(e)
.saturating_sub(1)
.saturating_add(more.len()),
),
None => match more.iter().position(|&b| b == b'=') {
Some(e) => (
held.len().saturating_add(e),
true,
more.len().saturating_sub(e).saturating_sub(1),
),
None => (held.len().saturating_add(more.len()), false, 0),
},
};
self.budget
.admit_partial(at, name_len, has_equals, value_len)
}
fn hold(&mut self, bytes: &[u8]) {
if bytes.is_empty() {
return;
}
if self.held == 0 {
self.held_at = self.seen;
}
let start = self.held;
let end = start.saturating_add(bytes.len());
if let Some(dst) = self.scratch.as_mut().get_mut(start..end) {
dst.copy_from_slice(bytes);
self.held = end;
}
}
}
impl<B: AsMut<[u8]> + AsRef<[u8]>> Drop for UrlencodedParser<B> {
fn drop(&mut self) {
crate::wipe::wipe(self.scratch.as_mut());
}
}
#[cfg(feature = "alloc")]
impl UrlencodedParser<alloc::vec::Vec<u8>> {
pub fn new(limits: &Limits) -> Result<Self, FormError> {
limits.validate()?;
Self::with_buffer(limits, alloc::vec![0u8; limits.urlencoded_buffer_len()])
}
}