sailfish/sailfish/src/escape.rs

90 lines
3.0 KiB
Rust

use array_vec::ArrayStr;
use super::Buffer;
/// A scheme for escaping strings.
pub trait Escape {
/// The type of an escaped character.
type Escaped: AsRef<str>;
/// True if `true` and `false` will never need escaping.
const IDENT_BOOLS: bool = false;
/// True if unsigned integers will never need escaping.
const IDENT_UINTS: bool = false;
/// True if signed integers will never need escaping.
const IDENT_INTS: bool = false;
/// True if floats (using [`ryu`]'s formatting) will never need escaping.
const IDENT_FLOATS: bool = false;
/// If the character needs to be escaped, does so and returns it as a string. Otherwise,
/// returns `None`.
fn escape(&self, c: char) -> Option<Self::Escaped>;
/// Writes the `string` to the `buffer`, applying any necessary escaping.
#[inline]
fn escape_to_buf(&self, buffer: &mut Buffer, string: &str) {
buffer.reserve(string.len());
let mut i = 0;
for (j, c) in string.char_indices() {
if let Some(rep) = self.escape(c) {
buffer.push_str(&string[i..j]);
buffer.push_str(rep.as_ref());
i = j + c.len_utf8();
}
}
}
/// Writes the `string` to the `buffer`, applying any necessary escaping.
///
/// # Examples
///
/// ```
/// use sailfish::{Escape, EscapeHtml};
///
/// let mut buf = String::new();
/// EscapeHtml.escape_to_string(&mut buf, "<h1>Hello, world!</h1>");
/// assert_eq!(buf, "&lt;h1&gt;Hello, world!&lt;/h1&gt;");
/// ```
#[inline]
fn escape_to_string(&self, buffer: &mut String, string: &str) {
let mut buf = Buffer::from(std::mem::take(buffer));
self.escape_to_buf(&mut buf, string);
*buffer = buf.into_string();
}
}
/// A scheme for escaping strings for safe insertion into JSON strings.
pub struct EscapeJsonString;
impl Escape for EscapeJsonString {
type Escaped = ArrayStr<4>;
const IDENT_BOOLS: bool = true;
const IDENT_UINTS: bool = true;
const IDENT_INTS: bool = true;
const IDENT_FLOATS: bool = true;
#[inline]
fn escape(&self, c: char) -> Option<Self::Escaped> {
match c {
'"' => Some(ArrayStr::try_from(r#"\""#).unwrap()),
'\\' => Some(ArrayStr::try_from(r"\\").unwrap()),
'\u{0000}'..='\u{001F}' => {
let c = c as u8;
let mut s = ArrayStr::try_from(r"\u").unwrap();
unsafe {
const HEX_DIGITS: [u8; 16] = *b"0123456789ABCDEF";
// SAFETY: we only write valid UTF-8
let arr = s.data_mut();
arr.unused_mut()[0].write(HEX_DIGITS[usize::from(c >> 4)]);
arr.unused_mut()[1].write(HEX_DIGITS[usize::from(c & 0xF)]);
// SAFETY: we just initialized the last 2 bytes
arr.set_len(arr.len() + 2);
}
Some(s)
}
_ => None,
}
}
}