use crate::Error::InsufficientTargetSpace;
use crate::hex::{HexDecoder, HexValidator};
use crate::{Decoder, Error};
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)]
#[non_exhaustive]
pub struct PercentDecoder {}
impl PercentDecoder {
#[inline(always)]
fn prefix_is_encoded(data: &[u8]) -> bool {
debug_assert!(!data.is_empty());
debug_assert_eq!(data[0], b'%');
data.len() >= 3
&& HexValidator::CASELESS.is_valid_byte(data[1])
&& HexValidator::CASELESS.is_valid_byte(data[2])
}
}
impl Decoder for PercentDecoder {
fn decoded_len(&self, data: &[u8]) -> Result<usize, Error> {
let encoded: usize = data
.iter()
.enumerate()
.filter(|(_, c)| **c == b'%')
.filter(|(i, _)| Self::prefix_is_encoded(&data[*i..]))
.count();
Ok(data.len() - (encoded * 2))
}
fn decode_to_slice(&self, data: &[u8], target: &mut [u8]) -> Result<usize, Error> {
let decoded_len: usize = self.decoded_len(data)?;
if decoded_len > target.len() {
Err(InsufficientTargetSpace)
} else {
let mut d: usize = 0;
let mut t: usize = 0;
while d < data.len() {
let c: u8 = data[d];
if c == b'%' && Self::prefix_is_encoded(&data[d..]) {
target[t] = HexDecoder::decode_bytes(data[d + 1], data[d + 2]);
t += 1;
d += 3;
} else {
target[t] = c;
t += 1;
d += 1;
}
}
debug_assert_eq!(t, decoded_len);
Ok(t)
}
}
}
#[cfg(test)]
#[cfg(feature = "dev")]
mod tests {
use crate::Decoder;
use crate::percent::PercentDecoder;
use crate::test::test_decoder;
#[test]
fn decode() {
let test_cases: &[(&str, &str)] = &[
("", ""),
("azAZ09+-. ", "azAZ09+-. "),
("你好", "你好"),
("%", "%"),
("%0", "%0"),
("%12", "\x12"),
("%0x", "%0x"),
("%x0", "%x0"),
("%%00", "%\x00"),
("%GG", "%GG"),
("a%GG", "a%GG"),
];
let decoder: PercentDecoder = PercentDecoder::default();
test_decoder(&decoder, test_cases);
}
#[test]
fn decode_insufficient_space() {
let decoder: PercentDecoder = PercentDecoder::default();
let mut target: Vec<u8> = vec![];
assert!(matches!(
decoder.decode_to_slice(b"%FF", &mut target),
Err(crate::Error::InsufficientTargetSpace)
));
}
}