aboutsummaryrefslogtreecommitdiffstats
path: root/askama_escape/src
diff options
context:
space:
mode:
Diffstat (limited to 'askama_escape/src')
-rw-r--r--askama_escape/src/lib.rs56
1 files changed, 52 insertions, 4 deletions
diff --git a/askama_escape/src/lib.rs b/askama_escape/src/lib.rs
index ad08e55..1788843 100644
--- a/askama_escape/src/lib.rs
+++ b/askama_escape/src/lib.rs
@@ -1,10 +1,7 @@
-#![no_std]
+#![cfg_attr(not(any(feature = "json", test)), no_std)]
#![deny(elided_lifetimes_in_paths)]
#![deny(unreachable_pub)]
-#[cfg(test)]
-extern crate std;
-
use core::fmt::{self, Display, Formatter, Write};
use core::str;
@@ -175,6 +172,57 @@ pub trait Escaper {
const FLAG: u8 = b'>' - b'"';
+/// Escape chevrons, ampersand and apostrophes for use in JSON
+#[cfg(feature = "json")]
+#[derive(Debug, Clone, Default)]
+pub struct JsonEscapeBuffer(Vec<u8>);
+
+#[cfg(feature = "json")]
+impl JsonEscapeBuffer {
+ pub fn new() -> Self {
+ Self(Vec::new())
+ }
+
+ pub fn finish(self) -> String {
+ unsafe { String::from_utf8_unchecked(self.0) }
+ }
+}
+
+#[cfg(feature = "json")]
+impl std::io::Write for JsonEscapeBuffer {
+ fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
+ 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;
+ }};
+ }
+
+ 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"#),
+ _ => (),
+ }
+ }
+ if start < bytes.len() {
+ self.0.extend_from_slice(&bytes[start..]);
+ }
+ Ok(bytes.len())
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ Ok(())
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;