use std::{io::prelude::*, result};
use thiserror::Error;
pub trait Escape {
fn escaped(&self) -> String;
}
pub trait Unescape {
fn unescape(&self) -> EscapeResult<Vec<u8>>;
}
pub type EscapeResult<T> = result::Result<T, EscapeError>;
#[derive(Error, Debug)]
pub enum EscapeError {
#[error("Invalid hex character: {0:?}")]
InvalidHexCharacter(u8),
#[error("Invalid hex length")]
InvalidHexLength,
}
impl Escape for [u8] {
fn escaped(&self) -> String {
let mut result = vec![];
for &ch in self.iter() {
if (b'!'..=b'~').contains(&ch) && ch != b'=' && ch != b'[' && ch != b']' {
result.push(ch);
} else {
write!(&mut result, "={:02x}", ch).unwrap();
}
}
String::from_utf8(result).unwrap()
}
}
impl Unescape for str {
fn unescape(&self) -> EscapeResult<Vec<u8>> {
let mut buf = Vec::with_capacity(self.len() / 2);
let mut phase = 0;
let mut tmp = 0;
for byte in self.bytes() {
if phase == 0 {
if byte == b'=' {
phase = 1;
} else {
buf.push(byte);
}
} else {
tmp <<= 4;
match byte {
b'A'..=b'F' => tmp |= byte - b'A' + 10,
b'a'..=b'f' => tmp |= byte - b'a' + 10,
b'0'..=b'9' => tmp |= byte - b'0',
_ => return Err(EscapeError::InvalidHexCharacter(byte)),
}
phase += 1;
if phase == 3 {
buf.push(tmp);
phase = 0;
tmp = 0;
}
}
}
if phase != 0 {
return Err(EscapeError::InvalidHexLength);
}
Ok(buf)
}
}
#[test]
fn test_unescape() {
macro_rules! assert_error_kind {
( $expr:expr, $kind:pat ) => {
match $expr {
Err($kind) => (),
Err(e) => panic!(
"Unexpected error kind: {:?} (want {})",
e,
stringify!($kind)
),
Ok(_) => panic!("Unexpected success"),
}
};
}
assert_eq!("=00".unescape().unwrap(), vec![0]);
assert_error_kind!("=00=0".unescape(), EscapeError::InvalidHexLength);
assert_error_kind!("=00=".unescape(), EscapeError::InvalidHexLength);
assert_error_kind!("=4g".unescape(), EscapeError::InvalidHexCharacter(b'g'));
}
#[test]
fn test_escape() {
let buf: Vec<u8> = (0u32..256).map(|i| i as u8).collect();
let text = (&buf[..]).escaped();
assert_eq!(text.unescape().unwrap(), buf);
}