From d4c674064e0e95bbc68c99f7e458d0128020ac7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Wed, 13 Apr 2022 11:50:10 +0200 Subject: [PATCH] =?UTF-8?q?Remove=20`unsafe=20{=20=E2=80=A6=20}`=20code=20?= =?UTF-8?q?from=20askama=5Fescape?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 , licensed as `MIT OR Apache-2.0`. [markup]: https://crates.io/crates/markup [escape.rs]: https://github.com/utkarshkukreti/markup.rs/blob/8ec40428483790b2c296e907e7be4147b157fe8f/markup/src/escape.rs#L1-L21 --- askama_escape/src/lib.rs | 84 ++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/askama_escape/src/lib.rs b/askama_escape/src/lib.rs index 178884308..8e579066e 100644 --- a/askama_escape/src/lib.rs +++ b/askama_escape/src/lib.rs @@ -107,40 +107,31 @@ where 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 { fn write_escaped(&self, mut fmt: W, string: &str) -> fmt::Result where W: Write, { - let bytes = string.as_bytes(); - let mut start = 0; - for (i, b) in bytes.iter().enumerate() { - if b.wrapping_sub(b'"') <= FLAG { - match *b { - b'<' => escaping_body!(start, i, fmt, bytes, "<"), - b'>' => escaping_body!(start, i, fmt, bytes, ">"), - b'&' => escaping_body!(start, i, fmt, bytes, "&"), - b'"' => escaping_body!(start, i, fmt, bytes, """), - b'\'' => escaping_body!(start, i, fmt, bytes, "'"), - _ => (), - } + let mut last = 0; + for (index, byte) in string.bytes().enumerate() { + macro_rules! go { + ($expr:expr) => {{ + fmt.write_str(&string[last..index])?; + fmt.write_str($expr)?; + last = index + 1; + }}; + } + + match byte { + b'<' => go!("<"), + b'>' => go!(">"), + b'&' => go!("&"), + b'"' => go!("""), + b'\'' => go!("'"), + _ => {} } } - if start < bytes.len() { - fmt.write_str(unsafe { str::from_utf8_unchecked(&bytes[start..]) }) - } else { - Ok(()) - } + fmt.write_str(&string[last..]) } } @@ -170,8 +161,6 @@ pub trait Escaper { W: Write; } -const FLAG: u8 = b'>' - b'"'; - /// Escape chevrons, ampersand and apostrophes for use in JSON #[cfg(feature = "json")] #[derive(Debug, Clone, Default)] @@ -191,30 +180,25 @@ impl JsonEscapeBuffer { #[cfg(feature = "json")] impl std::io::Write for JsonEscapeBuffer { fn write(&mut self, bytes: &[u8]) -> std::io::Result { - macro_rules! push_esc_sequence { - ($start:ident, $i:ident, $self:ident, $bytes:ident, $quote:expr) => {{ - if $start < $i { - $self.0.extend_from_slice(&$bytes[$start..$i]); - } - $self.0.extend_from_slice($quote); - $start = $i + 1; - }}; - } + let mut last = 0; + for (index, byte) in bytes.iter().enumerate() { + macro_rules! go { + ($expr:expr) => {{ + self.0.extend(&bytes[last..index]); + self.0.extend($expr); + last = index + 1; + }}; + } - self.0.reserve(bytes.len()); - let mut start = 0; - for (i, b) in bytes.iter().enumerate() { - match *b { - b'&' => push_esc_sequence!(start, i, self, bytes, br#"\u0026"#), - 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"#), - _ => (), + match byte { + b'&' => go!(br#"\u0026"#), + b'\'' => go!(br#"\u0027"#), + b'<' => go!(br#"\u003c"#), + b'>' => go!(br#"\u003e"#), + _ => {} } } - if start < bytes.len() { - self.0.extend_from_slice(&bytes[start..]); - } + self.0.extend(&bytes[last..]); Ok(bytes.len()) }