use std::borrow::Cow;
#[inline]
fn hex_digit(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
pub(super) fn unescape(input: &[u8]) -> Cow<'_, [u8]> {
let Some(first_bs) = input.iter().position(|&b| b == b'\\') else {
return Cow::Borrowed(input);
};
let mut out = Vec::with_capacity(input.len());
out.extend_from_slice(&input[..first_bs]);
let mut i = first_bs;
while i < input.len() {
let b = input[i];
if b != b'\\' {
out.push(b);
i += 1;
continue;
}
if i + 1 < input.len() && input[i + 1] == b'\\' {
out.push(b'\\');
i += 2;
continue;
}
if i + 2 < input.len()
&& let (Some(hi), Some(lo)) = (hex_digit(input[i + 1]), hex_digit(input[i + 2]))
{
out.push(hi * 16 + lo);
i += 3;
continue;
}
out.push(b'\\');
i += 1;
}
Cow::Owned(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_escapes_borrows() {
let s = b"plain text 123";
let out = unescape(s);
assert!(matches!(out, Cow::Borrowed(_)));
assert_eq!(&*out, &b"plain text 123"[..]);
}
#[test]
fn double_backslash_collapses() {
let out = unescape(br"a\\b");
assert_eq!(&*out, b"a\\b");
assert!(matches!(out, Cow::Owned(_)));
}
#[test]
fn hex_escape_decodes() {
let out = unescape(br"x\41\22");
assert_eq!(&*out, b"xA\"");
}
#[test]
fn nul_byte_escape() {
let out = unescape(br"\00");
assert_eq!(&*out, &[0u8][..]);
}
#[test]
fn mangling_prefix_decodes() {
let out = unescape(br"\01_foo");
assert_eq!(&*out, &[1u8, b'_', b'f', b'o', b'o'][..]);
}
#[test]
fn lenient_keeps_bad_backslash_at_eof() {
let out = unescape(br"x\");
assert_eq!(&*out, b"x\\");
}
#[test]
fn lenient_keeps_bad_hex() {
let out = unescape(br"\xZ");
assert_eq!(&*out, b"\\xZ");
}
#[test]
fn lenient_keeps_one_hex_at_eof() {
let out = unescape(br"\4");
assert_eq!(&*out, b"\\4");
}
#[test]
fn empty_input_borrows() {
let out = unescape(b"");
assert!(matches!(out, Cow::Borrowed(_)));
assert!(out.is_empty());
}
#[test]
fn mixed_escapes_and_text() {
let out = unescape(br"hello\20world\\!");
assert_eq!(&*out, b"hello world\\!");
}
}