Remove unsafe { … } code from askama_escape

Using only safe code is actually same as fast as the previous "unsafe"
code according to the crate's benchmark.

The code was extracted from [markup]'s escape function in [escape.rs],
written by Utkarsh Kukreti <utkarshkukreti@gmail.com>, licensed as
`MIT OR Apache-2.0`.

[markup]: https://crates.io/crates/markup
[escape.rs]: 8ec4042848/markup/src/escape.rs (L1-L21)
This commit is contained in:
René Kijewski 2022-04-13 11:50:10 +02:00 committed by Dirkjan Ochtman
parent ab39ced64e
commit fc779e22c3

View File

@ -107,40 +107,31 @@ where
pub struct Html; pub struct Html;
macro_rules! escaping_body {
($start:ident, $i:ident, $fmt:ident, $bytes:ident, $quote:expr) => {{
if $start < $i {
$fmt.write_str(unsafe { str::from_utf8_unchecked(&$bytes[$start..$i]) })?;
}
$fmt.write_str($quote)?;
$start = $i + 1;
}};
}
impl Escaper for Html { impl Escaper for Html {
fn write_escaped<W>(&self, mut fmt: W, string: &str) -> fmt::Result fn write_escaped<W>(&self, mut fmt: W, string: &str) -> fmt::Result
where where
W: Write, W: Write,
{ {
let bytes = string.as_bytes(); let mut last = 0;
let mut start = 0; for (index, byte) in string.bytes().enumerate() {
for (i, b) in bytes.iter().enumerate() { macro_rules! go {
if b.wrapping_sub(b'"') <= FLAG { ($expr:expr) => {{
match *b { fmt.write_str(&string[last..index])?;
b'<' => escaping_body!(start, i, fmt, bytes, "&lt;"), fmt.write_str($expr)?;
b'>' => escaping_body!(start, i, fmt, bytes, "&gt;"), last = index + 1;
b'&' => escaping_body!(start, i, fmt, bytes, "&amp;"), }};
b'"' => escaping_body!(start, i, fmt, bytes, "&quot;"), }
b'\'' => escaping_body!(start, i, fmt, bytes, "&#x27;"),
_ => (), match byte {
} b'<' => go!("&lt;"),
b'>' => go!("&gt;"),
b'&' => go!("&amp;"),
b'"' => go!("&quot;"),
b'\'' => go!("&#x27;"),
_ => {}
} }
} }
if start < bytes.len() { fmt.write_str(&string[last..])
fmt.write_str(unsafe { str::from_utf8_unchecked(&bytes[start..]) })
} else {
Ok(())
}
} }
} }
@ -170,8 +161,6 @@ pub trait Escaper {
W: Write; W: Write;
} }
const FLAG: u8 = b'>' - b'"';
/// Escape chevrons, ampersand and apostrophes for use in JSON /// Escape chevrons, ampersand and apostrophes for use in JSON
#[cfg(feature = "json")] #[cfg(feature = "json")]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -191,30 +180,25 @@ impl JsonEscapeBuffer {
#[cfg(feature = "json")] #[cfg(feature = "json")]
impl std::io::Write for JsonEscapeBuffer { impl std::io::Write for JsonEscapeBuffer {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> { fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
macro_rules! push_esc_sequence { let mut last = 0;
($start:ident, $i:ident, $self:ident, $bytes:ident, $quote:expr) => {{ for (index, byte) in bytes.iter().enumerate() {
if $start < $i { macro_rules! go {
$self.0.extend_from_slice(&$bytes[$start..$i]); ($expr:expr) => {{
} self.0.extend(&bytes[last..index]);
$self.0.extend_from_slice($quote); self.0.extend($expr);
$start = $i + 1; last = index + 1;
}}; }};
} }
self.0.reserve(bytes.len()); match byte {
let mut start = 0; b'&' => go!(br#"\u0026"#),
for (i, b) in bytes.iter().enumerate() { b'\'' => go!(br#"\u0027"#),
match *b { b'<' => go!(br#"\u003c"#),
b'&' => push_esc_sequence!(start, i, self, bytes, br#"\u0026"#), b'>' => go!(br#"\u003e"#),
b'\'' => push_esc_sequence!(start, i, self, bytes, br#"\u0027"#), _ => {}
b'<' => push_esc_sequence!(start, i, self, bytes, br#"\u003c"#),
b'>' => push_esc_sequence!(start, i, self, bytes, br#"\u003e"#),
_ => (),
} }
} }
if start < bytes.len() { self.0.extend(&bytes[last..]);
self.0.extend_from_slice(&bytes[start..]);
}
Ok(bytes.len()) Ok(bytes.len())
} }