diff options
Diffstat (limited to 'askama_shared')
-rw-r--r-- | askama_shared/src/escaping.rs | 103 | ||||
-rw-r--r-- | askama_shared/src/filters/json.rs | 6 | ||||
-rw-r--r-- | askama_shared/src/filters/mod.rs | 9 |
3 files changed, 60 insertions, 58 deletions
diff --git a/askama_shared/src/escaping.rs b/askama_shared/src/escaping.rs index 930dbbe..8bb8f0b 100644 --- a/askama_shared/src/escaping.rs +++ b/askama_shared/src/escaping.rs @@ -1,4 +1,5 @@ use std::fmt::{self, Display, Formatter}; +use std::str; #[derive(Debug, PartialEq)] pub enum MarkupDisplay<T> @@ -19,11 +20,6 @@ where _ => self, } } - pub fn unsafe_string(&self) -> String { - match *self { - MarkupDisplay::Safe(ref t) | MarkupDisplay::Unsafe(ref t) => format!("{}", t), - } - } } impl<T> From<T> for MarkupDisplay<T> @@ -41,58 +37,65 @@ where { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match *self { - MarkupDisplay::Unsafe(_) => write!(f, "{}", escape(self.unsafe_string())), + MarkupDisplay::Unsafe(ref t) => escape(&t.to_string()).fmt(f), MarkupDisplay::Safe(ref t) => t.fmt(f), } } } const FLAG: u8 = b'>' - b'"'; -pub fn escape(s: String) -> String { - let mut found = None; - for (i, b) in s.as_bytes().iter().enumerate() { - if b.wrapping_sub(b'"') <= FLAG { - match *b { - b'<' | b'>' | b'&' | b'"' | b'\'' | b'/' => { - found = Some(i); - break; - } - _ => (), - }; - } + +pub fn escape(s: &str) -> Escaped { + Escaped { + bytes: s.as_bytes(), } +} - if let Some(found) = found { - let bytes = s.as_bytes(); - let mut res = Vec::with_capacity(s.len() + 6); - res.extend(&bytes[0..found]); - for c in bytes[found..].iter() { - match *c { - b'<' => { - res.extend(b"<"); - } - b'>' => { - res.extend(b">"); - } - b'&' => { - res.extend(b"&"); - } - b'"' => { - res.extend(b"""); +pub struct Escaped<'a> { + bytes: &'a [u8], +} + +enum State { + Empty, + Unescaped(usize), +} + +impl<'a> ::std::fmt::Display for Escaped<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + use self::State::*; + let mut state = Empty; + for (i, b) in self.bytes.iter().enumerate() { + let next = if b.wrapping_sub(b'"') <= FLAG { + match *b { + b'<' => Some("<"), + b'>' => Some(">"), + b'&' => Some("&"), + b'"' => Some("""), + b'\'' => Some("'"), + b'/' => Some("/"), + _ => None, } - b'\'' => { - res.extend(b"'"); + } else { + None + }; + state = match (state, next) { + (Empty, None) => Unescaped(i), + (s @ Unescaped(_), None) => s, + (Empty, Some(escaped)) => { + fmt.write_str(escaped)?; + Empty } - b'/' => { - res.extend(b"/"); + (Unescaped(start), Some(escaped)) => { + fmt.write_str(unsafe { str::from_utf8_unchecked(&self.bytes[start..i]) })?; + fmt.write_str(escaped)?; + Empty } - _ => res.push(*c), - } + }; } - - String::from_utf8(res).unwrap() - } else { - s + if let Unescaped(start) = state { + fmt.write_str(unsafe { str::from_utf8_unchecked(&self.bytes[start..]) })?; + } + Ok(()) } } @@ -101,10 +104,10 @@ mod tests { use super::*; #[test] fn test_escape() { - assert_eq!(escape("".to_string()), ""); - assert_eq!(escape("<&>".to_string()), "<&>"); - assert_eq!(escape("bla&".to_string()), "bla&"); - assert_eq!(escape("<foo".to_string()), "<foo"); - assert_eq!(escape("bla&h".to_string()), "bla&h"); + assert_eq!(escape("").to_string(), ""); + assert_eq!(escape("<&>").to_string(), "<&>"); + assert_eq!(escape("bla&").to_string(), "bla&"); + assert_eq!(escape("<foo").to_string(), "<foo"); + assert_eq!(escape("bla&h").to_string(), "bla&h"); } } diff --git a/askama_shared/src/filters/json.rs b/askama_shared/src/filters/json.rs index ffc5310..85d4a32 100644 --- a/askama_shared/src/filters/json.rs +++ b/askama_shared/src/filters/json.rs @@ -22,10 +22,10 @@ mod tests { #[test] fn test_json() { - assert_eq!(json(&true).unwrap().unsafe_string(), "true"); - assert_eq!(json(&"foo").unwrap().unsafe_string(), r#""foo""#); + assert_eq!(json(&true).unwrap().to_string(), "true"); + assert_eq!(json(&"foo").unwrap().to_string(), r#""foo""#); assert_eq!( - json(&vec!["foo", "bar"]).unwrap().unsafe_string(), + json(&vec!["foo", "bar"]).unwrap().to_string(), r#"[ "foo", "bar" diff --git a/askama_shared/src/filters/mod.rs b/askama_shared/src/filters/mod.rs index a6d2ba6..d5c4bd0 100644 --- a/askama_shared/src/filters/mod.rs +++ b/askama_shared/src/filters/mod.rs @@ -16,7 +16,7 @@ use num_traits::Signed; use std::fmt; use super::Result; -use escaping::{self, MarkupDisplay}; +use escaping::MarkupDisplay; // This is used by the code generator to decide whether a named filter is part of // Askama or should refer to a local `filters` module. It should contain all the @@ -60,17 +60,16 @@ where } /// Escapes `&`, `<` and `>` in strings -pub fn escape<D, I>(i: I) -> Result<MarkupDisplay<String>> +pub fn escape<D, I>(i: I) -> Result<MarkupDisplay<D>> where D: fmt::Display, MarkupDisplay<D>: From<I>, { - let md: MarkupDisplay<D> = i.into(); - Ok(MarkupDisplay::Safe(escaping::escape(md.unsafe_string()))) + Ok(i.into()) } /// Alias for the `escape()` filter -pub fn e<D, I>(i: I) -> Result<MarkupDisplay<String>> +pub fn e<D, I>(i: I) -> Result<MarkupDisplay<D>> where D: fmt::Display, MarkupDisplay<D>: From<I>, |