use alloc::{borrow::Cow, vec::Vec};
use crate::tree::codec::mode::VcardEscaper;
pub(crate) fn escape_with(bytes: &[u8], escaper: VcardEscaper) -> Cow<'_, [u8]> {
match escaper {
VcardEscaper::Modern => escape_modern(bytes),
VcardEscaper::V2_1 => escape_v21(bytes),
}
}
fn escape_modern(bytes: &[u8]) -> Cow<'_, [u8]> {
if !bytes
.iter()
.any(|b| matches!(b, b'\\' | b',' | b';' | b'\n'))
{
return Cow::Borrowed(bytes);
}
let mut out = Vec::with_capacity(bytes.len());
for &b in bytes {
match b {
b'\\' => out.extend_from_slice(b"\\\\"),
b',' => out.extend_from_slice(b"\\,"),
b';' => out.extend_from_slice(b"\\;"),
b'\n' => out.extend_from_slice(b"\\n"),
other => out.push(other),
}
}
Cow::Owned(out)
}
fn escape_v21(bytes: &[u8]) -> Cow<'_, [u8]> {
if !bytes.contains(&b';') {
return Cow::Borrowed(bytes);
}
let mut out = Vec::with_capacity(bytes.len() + 2);
for &b in bytes {
if b == b';' {
out.push(b'\\');
}
out.push(b);
}
Cow::Owned(out)
}
#[cfg(test)]
mod tests {
use alloc::borrow::Cow;
use crate::tree::codec::{escape::escape_with, mode::VcardEscaper};
#[test]
fn escapes_separators_and_newlines_and_borrows_when_clean() {
assert_eq!(
escape_with(b"a,b;c\nd", VcardEscaper::Modern).as_ref(),
br"a\,b\;c\nd".as_slice(),
);
assert!(matches!(
escape_with(b"plain", VcardEscaper::Modern),
Cow::Borrowed(b"plain")
));
assert_eq!(
escape_with(b"a,b;c", VcardEscaper::V2_1).as_ref(),
br"a,b\;c".as_slice(),
);
}
#[test]
fn a_literal_backslash_doubles_and_resolves_back() {
use crate::tree::codec::unescape::unescape_with;
assert_eq!(
escape_with(br"C:\path", VcardEscaper::Modern).as_ref(),
br"C:\\path".as_slice(),
);
assert_eq!(
unescape_with(br"C:\\path", VcardEscaper::Modern),
r"C:\path",
);
assert_eq!(
escape_with(br"trailing\", VcardEscaper::Modern).as_ref(),
br"trailing\\".as_slice(),
);
assert_eq!(
unescape_with(br"dangling\", VcardEscaper::Modern),
r"dangling\"
);
}
}