use core::fmt;
use crate::error::{FormError, Limit, Location};
use crate::limits::{Limits, PercentPolicy};
use crate::urlencoded::decode::decode_component;
const HAS_EQUALS: u8 = 1;
const NAME_DECODE: u8 = 2;
const VALUE_DECODE: u8 = 4;
#[must_use]
pub fn raw_pairs<'a>(input: &'a [u8], limits: &Limits) -> RawPairs<'a> {
RawPairs {
input,
pos: 0,
budget: Budget::new(limits),
done: false,
checked_body: false,
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Budget {
max_fields: u32,
max_name: u32,
max_value: u32,
max_text: u64,
pub(crate) body: u64,
fields: u32,
text: u64,
}
impl Budget {
pub(crate) const fn new(limits: &Limits) -> Budget {
Budget {
max_fields: limits.max_fields,
max_name: limits.name(),
max_value: limits.value(),
max_text: limits.text(),
body: limits.body(),
fields: 0,
text: 0,
}
}
pub(crate) fn admit(&mut self, pair: &RawPair<'_>) -> Result<(), FormError> {
let at = Location::at(pair.offset);
self.fields = self.fields.saturating_add(1);
if self.fields > self.max_fields {
return Err(FormError::limit(Limit::Fields, at));
}
if pair.name.len() as u64 > u64::from(self.max_name) {
return Err(FormError::limit(Limit::NameLength, at));
}
let value_at = pair.value_offset();
if pair.value.len() as u64 > u64::from(self.max_value) {
return Err(FormError::limit(Limit::ValueLength, value_at));
}
self.text = self.text.saturating_add(pair.value.len() as u64);
if self.text > self.max_text {
return Err(FormError::limit(Limit::TextTotal, value_at));
}
Ok(())
}
pub(crate) fn admit_partial(
&self,
offset: u64,
name_len: usize,
has_equals: bool,
value_len: usize,
) -> Result<(), FormError> {
let name_over = name_len as u64 > u64::from(self.max_name);
let value_over = has_equals && value_len as u64 > u64::from(self.max_value);
if !name_over && !value_over {
return Ok(());
}
if self.fields.saturating_add(1) > self.max_fields {
return Err(FormError::limit(Limit::Fields, Location::at(offset)));
}
if name_over {
return Err(FormError::limit(Limit::NameLength, Location::at(offset)));
}
let value_at = offset.saturating_add(name_len as u64).saturating_add(1);
Err(FormError::limit(Limit::ValueLength, Location::at(value_at)))
}
pub(crate) fn reset(&mut self) {
self.fields = 0;
self.text = 0;
}
}
#[inline]
fn scan(input: &[u8], start: usize) -> (usize, Option<usize>, u8) {
let mut flags = 0u8;
let mut i = start;
let eq = loop {
i = if flags & NAME_DECODE == 0 {
crate::scan::find_any(input, i, *b"&=%+")
} else {
crate::scan::find_any(input, i, *b"&=")
};
match input.get(i) {
None | Some(b'&') => return (i, None, flags),
Some(b'=') => break i,
Some(_) => flags |= NAME_DECODE,
}
i = i.wrapping_add(1);
};
flags |= HAS_EQUALS;
i = eq.wrapping_add(1);
loop {
i = if flags & VALUE_DECODE == 0 {
crate::scan::find_any(input, i, *b"&%+")
} else {
crate::scan::find_byte(input, i, b'&')
};
match input.get(i) {
None | Some(b'&') => return (i, Some(eq), flags),
Some(_) => flags |= VALUE_DECODE,
}
i = i.wrapping_add(1);
}
}
#[derive(Clone)]
pub struct RawPairs<'a> {
input: &'a [u8],
pos: usize,
budget: Budget,
done: bool,
checked_body: bool,
}
impl fmt::Debug for RawPairs<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawPairs")
.field("position", &self.pos)
.field("fields", &self.budget.fields)
.finish_non_exhaustive()
}
}
impl<'a> RawPairs<'a> {
fn fail(&mut self, err: FormError) -> Option<Result<RawPair<'a>, FormError>> {
self.done = true;
Some(Err(err))
}
}
impl<'a> Iterator for RawPairs<'a> {
type Item = Result<RawPair<'a>, FormError>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
if !self.checked_body {
self.checked_body = true;
if self.input.len() as u64 > self.budget.body {
return self.fail(FormError::limit(
Limit::Body,
Location::at(self.budget.body),
));
}
}
loop {
if self.pos >= self.input.len() {
self.done = true;
return None;
}
let start = self.pos;
let (end, eq, flags) = scan(self.input, start);
self.pos = end.saturating_add(1).min(self.input.len());
if end == start {
continue;
}
let seq = self.input.get(start..end).unwrap_or(&[]);
let (name, value) = match eq {
Some(e) => {
let at = e.wrapping_sub(start);
(
seq.get(..at).unwrap_or(&[]),
seq.get(at.wrapping_add(1)..).unwrap_or(&[]),
)
}
None => (seq, &[][..]),
};
let pair = RawPair {
name,
value,
flags,
offset: start as u64,
};
if let Err(e) = self.budget.admit(&pair) {
return self.fail(e);
}
return Some(Ok(pair));
}
}
}
impl core::iter::FusedIterator for RawPairs<'_> {}
#[derive(Clone, Copy)]
pub struct RawPair<'a> {
name: &'a [u8],
value: &'a [u8],
flags: u8,
offset: u64,
}
impl fmt::Debug for RawPair<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RawPair")
.field("offset", &self.offset)
.field("name_len", &self.name.len())
.field("value_len", &self.value.len())
.finish()
}
}
impl<'a> RawPair<'a> {
pub(crate) fn split(seq: &'a [u8], offset: u64) -> RawPair<'a> {
let (_, eq, flags) = scan(seq, 0);
let (name, value) = match eq {
Some(e) => (
seq.get(..e).unwrap_or(&[]),
seq.get(e.wrapping_add(1)..).unwrap_or(&[]),
),
None => (seq, &[][..]),
};
RawPair {
name,
value,
flags,
offset,
}
}
#[cfg(feature = "alloc")]
#[inline]
pub(crate) fn name_text(&self, whole: Option<&'a str>) -> Option<&'a str> {
if self.name_needs_decoding() {
return None;
}
let start = usize::try_from(self.offset).ok()?;
match whole {
Some(w) => w.get(start..start.checked_add(self.name.len())?),
None => core::str::from_utf8(self.name).ok(),
}
}
#[cfg(feature = "alloc")]
#[inline]
pub(crate) fn value_text(&self, whole: Option<&'a str>) -> Option<&'a str> {
if self.value_needs_decoding() {
return None;
}
if self.value.is_empty() {
return Some("");
}
let start = usize::try_from(self.offset)
.ok()?
.checked_add(self.name.len())?
.checked_add(1)?;
match whole {
Some(w) => w.get(start..start.checked_add(self.value.len())?),
None => core::str::from_utf8(self.value).ok(),
}
}
fn value_offset(&self) -> Location {
Location::at(
self.offset
.saturating_add(self.name.len() as u64)
.saturating_add(1),
)
}
#[must_use]
pub const fn name_raw(&self) -> &'a [u8] {
self.name
}
#[must_use]
pub const fn value_raw(&self) -> &'a [u8] {
self.value
}
#[must_use]
pub const fn has_equals(&self) -> bool {
self.flags & HAS_EQUALS != 0
}
#[must_use]
pub const fn name_needs_decoding(&self) -> bool {
self.flags & NAME_DECODE != 0
}
#[must_use]
pub const fn value_needs_decoding(&self) -> bool {
self.flags & VALUE_DECODE != 0
}
#[must_use]
pub fn name_str(&self) -> Option<&'a str> {
if self.name_needs_decoding() {
None
} else {
core::str::from_utf8(self.name).ok()
}
}
#[must_use]
pub fn value_str(&self) -> Option<&'a str> {
if self.value_needs_decoding() {
None
} else {
core::str::from_utf8(self.value).ok()
}
}
pub fn decode_name<'b>(
&self,
out: &'b mut [u8],
policy: PercentPolicy,
) -> Result<&'b str, FormError> {
decode_component(self.name, out, policy).map_err(|e| e.shifted(self.offset))
}
pub fn decode_value<'b>(
&self,
out: &'b mut [u8],
policy: PercentPolicy,
) -> Result<&'b str, FormError> {
let at = self
.offset
.saturating_add(self.name.len() as u64)
.saturating_add(1);
decode_component(self.value, out, policy).map_err(|e| e.shifted(at))
}
#[must_use]
pub const fn offset(&self) -> u64 {
self.offset
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
type Pairs<'a> = Vec<(&'a [u8], &'a [u8])>;
fn pairs(input: &[u8]) -> Result<Pairs<'_>, FormError> {
raw_pairs(input, &Limits::DEFAULT)
.map(|p| p.map(|p| (p.name_raw(), p.value_raw())))
.collect()
}
#[test]
fn test_raw_pairs_whatwg_splitting() {
assert_eq!(pairs(b"").unwrap(), []);
assert_eq!(pairs(b"&&&").unwrap(), []);
assert_eq!(pairs(b"=").unwrap(), [(&b""[..], &b""[..])]);
assert_eq!(pairs(b"a==b").unwrap(), [(&b"a"[..], &b"=b"[..])]);
assert_eq!(pairs(b"=a").unwrap(), [(&b""[..], &b"a"[..])]);
assert_eq!(pairs(b"a;b=c").unwrap(), [(&b"a;b"[..], &b"c"[..])]);
assert_eq!(
pairs(b"a=1&b=2&").unwrap(),
[(&b"a"[..], &b"1"[..]), (&b"b"[..], &b"2"[..])]
);
}
#[test]
fn test_raw_pairs_flags_per_side() {
let p = raw_pairs(b"a%=b", &Limits::DEFAULT)
.next()
.unwrap()
.unwrap();
assert!(p.name_needs_decoding() && !p.value_needs_decoding());
let p = raw_pairs(b"a=b+%", &Limits::DEFAULT)
.next()
.unwrap()
.unwrap();
assert!(!p.name_needs_decoding() && p.value_needs_decoding());
let p = raw_pairs(b"a=b=+", &Limits::DEFAULT)
.next()
.unwrap()
.unwrap();
assert!(p.value_needs_decoding() && p.has_equals());
}
#[test]
fn test_raw_pairs_field_limit_n_and_n_plus_one() {
let l = Limits::DEFAULT.with_max_fields(2);
assert_eq!(raw_pairs(b"a&b", &l).count(), 2);
let r: Result<Vec<_>, _> = raw_pairs(b"a&b&c", &l).collect();
assert!(matches!(
r,
Err(FormError::Limit {
limit: Limit::Fields,
at
}) if at.offset == 4
));
}
#[test]
fn test_raw_pairs_length_limits_at_the_edge() {
let l = Limits::DEFAULT
.with_max_name_bytes(3)
.with_max_value_bytes(3);
assert!(raw_pairs(b"abc=def", &l).all(|p| p.is_ok()));
let e = raw_pairs(b"abcd=1", &l).next().unwrap().unwrap_err();
assert!(matches!(
e,
FormError::Limit {
limit: Limit::NameLength,
..
}
));
let e = raw_pairs(b"a=defg", &l).next().unwrap().unwrap_err();
assert!(matches!(e, FormError::Limit { limit: Limit::ValueLength, at } if at.offset == 2));
let l = Limits::DEFAULT.with_max_text_bytes(4);
let r: Result<Vec<_>, _> = raw_pairs(b"a=12&b=345", &l).collect();
assert!(matches!(
r,
Err(FormError::Limit {
limit: Limit::TextTotal,
..
})
));
}
#[test]
fn test_raw_pairs_body_limit_counts_empty_sequences() {
let l = Limits::DEFAULT.with_max_body_bytes(4);
assert_eq!(raw_pairs(b"&&&&", &l).count(), 0);
let e = raw_pairs(b"&&&&&", &l).next().unwrap().unwrap_err();
assert!(matches!(
e,
FormError::Limit {
limit: Limit::Body,
..
}
));
}
#[test]
fn test_raw_pairs_stops_after_error() {
let l = Limits::DEFAULT.with_max_fields(0);
let mut it = raw_pairs(b"a&b", &l);
assert!(it.next().unwrap().is_err());
assert!(it.next().is_none());
}
#[test]
fn test_raw_pair_debug_hides_values() {
let p = raw_pairs(b"password=hunter2", &Limits::DEFAULT)
.next()
.unwrap()
.unwrap();
let s = alloc::format!("{p:?}");
assert!(!s.contains("hunter2") && !s.contains("password"));
}
}