use crate::error::{EncodingError, FormError, Location};
use crate::limits::PercentPolicy;
#[inline]
fn hex(b: Option<&u8>) -> Option<u8> {
parse_net::class::hex_value(*b?)
}
pub fn decode_into<'b>(
raw: &[u8],
out: &'b mut [u8],
policy: PercentPolicy,
) -> Result<&'b str, FormError> {
decode_component(raw, out, policy)
}
pub(crate) fn decode_component<'b>(
raw: &[u8],
out: &'b mut [u8],
policy: PercentPolicy,
) -> Result<&'b str, FormError> {
if out.len() < raw.len() {
return Err(FormError::BufferTooSmall { needed: raw.len() });
}
let n = decode_bytes(raw, out, policy)?;
let text = out.get(..n).unwrap_or(&[]);
core::str::from_utf8(text)
.map_err(|_| FormError::encoding(EncodingError::InvalidUtf8, Location::at(0)))
}
fn decode_bytes(raw: &[u8], out: &mut [u8], policy: PercentPolicy) -> Result<usize, FormError> {
let mut i = 0usize;
let mut w = 0usize;
while i < raw.len() {
let next = crate::scan::find_any(raw, i, *b"%+");
let run = raw.get(i..next).unwrap_or(&[]);
if let Some(dst) = out.get_mut(w..w.wrapping_add(run.len())) {
dst.copy_from_slice(run);
}
w = w.wrapping_add(run.len());
i = next;
let Some(&b) = raw.get(i) else { break };
let byte = if b == b'+' {
i = i.wrapping_add(1);
b' '
} else {
match (
hex(raw.get(i.wrapping_add(1))),
hex(raw.get(i.wrapping_add(2))),
) {
(Some(h), Some(l)) => {
i = i.wrapping_add(3);
(h << 4) | l
}
_ => match policy {
PercentPolicy::Literal => {
i = i.wrapping_add(1);
b'%'
}
_ => {
return Err(FormError::encoding(
EncodingError::InvalidPercent,
Location::at(i as u64),
));
}
},
}
};
if let Some(slot) = out.get_mut(w) {
*slot = byte;
}
w = w.wrapping_add(1);
}
Ok(w)
}
#[cfg(feature = "alloc")]
pub(crate) fn decode_to_vec(
raw: &[u8],
policy: PercentPolicy,
) -> Result<alloc::string::String, FormError> {
let mut buf = alloc::vec![0u8; raw.len()];
let n = decode_bytes(raw, &mut buf, policy)?;
buf.truncate(n);
alloc::string::String::from_utf8(buf)
.map_err(|_| FormError::encoding(EncodingError::InvalidUtf8, Location::at(0)))
}
#[cfg(feature = "alloc")]
pub(crate) fn decode_append(
raw: &[u8],
arena: &mut [u8],
at: usize,
policy: PercentPolicy,
) -> Result<usize, FormError> {
let Some(dst) = arena.get_mut(at..) else {
return Err(FormError::BufferTooSmall { needed: raw.len() });
};
if dst.len() < raw.len() {
return Err(FormError::BufferTooSmall { needed: raw.len() });
}
let n = decode_bytes(raw, dst, policy)?;
let text = dst.get(..n).unwrap_or(&[]);
if core::str::from_utf8(text).is_err() {
return Err(FormError::encoding(
EncodingError::InvalidUtf8,
Location::at(0),
));
}
Ok(n)
}
#[cfg(test)]
mod tests {
use super::*;
fn dec(raw: &[u8], policy: PercentPolicy) -> Result<alloc::string::String, FormError> {
let mut out = alloc::vec![0u8; raw.len()];
decode_into(raw, &mut out, policy).map(alloc::string::ToString::to_string)
}
#[test]
fn test_decode_plus_and_hex_both_cases() {
assert_eq!(dec(b"+%20+", PercentPolicy::Refuse).unwrap(), " ");
assert_eq!(dec(b"%2f%2F", PercentPolicy::Refuse).unwrap(), "//");
assert_eq!(dec(b"%2B", PercentPolicy::Refuse).unwrap(), "+");
assert_eq!(dec(b"", PercentPolicy::Refuse).unwrap(), "");
}
#[test]
fn test_decode_bad_percent_refused_or_literal() {
for (raw, at) in [
(&b"%"[..], 0),
(b"%4", 0),
(b"%4g", 0),
(b"%zz", 0),
(b"a%%41", 1),
] {
let e = dec(raw, PercentPolicy::Refuse).unwrap_err();
assert_eq!(
e,
FormError::encoding(EncodingError::InvalidPercent, Location::at(at)),
"{raw:?}"
);
}
assert_eq!(dec(b"%", PercentPolicy::Literal).unwrap(), "%");
assert_eq!(dec(b"%4g", PercentPolicy::Literal).unwrap(), "%4g");
assert_eq!(dec(b"%%41", PercentPolicy::Literal).unwrap(), "%A");
}
#[test]
fn test_decode_invalid_utf8_refused() {
for raw in [
&b"%C0%AF"[..],
b"%ED%A0%80",
b"%E2%82",
b"%FF",
b"%F8%88%80%80%80",
b"\xff",
] {
assert_eq!(
dec(raw, PercentPolicy::Refuse).unwrap_err(),
FormError::encoding(EncodingError::InvalidUtf8, Location::at(0)),
"{raw:?}"
);
}
assert_eq!(
dec(b"%EF%BB%BFx", PercentPolicy::Refuse).unwrap(),
"\u{feff}x"
);
assert_eq!(dec("é".as_bytes(), PercentPolicy::Refuse).unwrap(), "é");
}
#[test]
fn test_decode_short_buffer_refused_before_writing() {
let mut out = [7u8; 2];
assert_eq!(
decode_into(b"abc", &mut out, PercentPolicy::Refuse),
Err(FormError::BufferTooSmall { needed: 3 })
);
assert_eq!(out, [7, 7]);
}
#[test]
fn test_decode_never_longer_than_input() {
let raw = b"a%41+b%%";
let mut out = [0u8; 8];
let s = decode_into(raw, &mut out, PercentPolicy::Literal).unwrap();
assert!(s.len() <= raw.len());
}
}