pub fn escaped_len(bytes: &[u8]) -> u64 {
bytes.iter().map(|&byte| escaped_byte_len(byte)).sum()
}
fn escaped_byte_len(byte: u8) -> u64 {
match byte {
b'"' | b'\\' | b'\n' | b'\r' | b'\t' | 0x08 | 0x0c => 2,
0x00..=0x1f => 6,
_ => 1,
}
}
pub(crate) fn escape_into(bytes: &[u8], out: &mut Vec<u8>) {
const HEX: &[u8; 16] = b"0123456789abcdef";
for &byte in bytes {
match byte {
b'"' => out.extend_from_slice(b"\\\""),
b'\\' => out.extend_from_slice(b"\\\\"),
b'\n' => out.extend_from_slice(b"\\n"),
b'\r' => out.extend_from_slice(b"\\r"),
b'\t' => out.extend_from_slice(b"\\t"),
0x08 => out.extend_from_slice(b"\\b"),
0x0c => out.extend_from_slice(b"\\f"),
0x00..=0x1f => {
out.extend_from_slice(b"\\u00");
out.push(HEX[usize::from(byte >> 4)]);
out.push(HEX[usize::from(byte & 0xf)]);
}
_ => out.push(byte),
}
}
}
#[derive(Debug, Default)]
#[cfg_attr(not(feature = "client"), allow(dead_code))]
pub(crate) struct Utf8Check {
carry: Vec<u8>,
}
#[cfg_attr(not(feature = "client"), allow(dead_code))]
impl Utf8Check {
pub(crate) fn push(&mut self, block: &[u8]) -> bool {
let joined;
let bytes = if self.carry.is_empty() {
block
} else {
self.carry.extend_from_slice(block);
joined = std::mem::take(&mut self.carry);
&joined
};
match std::str::from_utf8(bytes) {
Ok(_) => true,
Err(error) if error.error_len().is_none() => {
self.carry = bytes[error.valid_up_to()..].to_vec();
true
}
Err(_) => false,
}
}
pub(crate) fn finish(&self) -> bool {
self.carry.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str =
"say \"hi\"\tC:\\temp\r\n\u{1}\u{8}\u{c}\u{1f}caf\u{e9} \u{20ac} \u{1f980}";
#[test]
fn escapes_exactly_as_serde_json_does() {
let mut out = vec![b'"'];
escape_into(SAMPLE.as_bytes(), &mut out);
out.push(b'"');
assert_eq!(out, serde_json::to_vec(SAMPLE).unwrap());
assert_eq!(escaped_len(SAMPLE.as_bytes()) + 2, out.len() as u64);
}
#[test]
fn escapes_the_same_whatever_the_blocks_are() {
let mut whole = Vec::new();
escape_into(SAMPLE.as_bytes(), &mut whole);
for size in 1..8 {
let mut pieces = Vec::new();
for block in SAMPLE.as_bytes().chunks(size) {
escape_into(block, &mut pieces);
}
assert_eq!(pieces, whole, "block size {size}");
}
}
#[test]
fn utf8_is_checked_across_blocks() {
for size in 1..8 {
let mut check = Utf8Check::default();
assert!(
SAMPLE
.as_bytes()
.chunks(size)
.all(|block| check.push(block))
);
assert!(check.finish(), "block size {size}");
}
let mut check = Utf8Check::default();
assert!(!check.push(b"ab\xffcd"));
let mut cut = Utf8Check::default();
assert!(cut.push(&"\u{20ac}".as_bytes()[..2]));
assert!(!cut.finish());
}
}