mirror of
https://github.com/askama-rs/askama.git
synced 2025-09-30 14:31:36 +00:00
Escape into Formatter
This commit is contained in:
parent
2ef8258e8f
commit
57dc7ef9ca
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
state = match (state, next) {
|
||||
(Empty, None) => Unescaped(i),
|
||||
(s @ Unescaped(_), None) => s,
|
||||
(Empty, Some(escaped)) => {
|
||||
fmt.write_str(escaped)?;
|
||||
Empty
|
||||
}
|
||||
(Unescaped(start), Some(escaped)) => {
|
||||
fmt.write_str(unsafe { str::from_utf8_unchecked(&self.bytes[start..i]) })?;
|
||||
fmt.write_str(escaped)?;
|
||||
Empty
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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""");
|
||||
}
|
||||
b'\'' => {
|
||||
res.extend(b"'");
|
||||
}
|
||||
b'/' => {
|
||||
res.extend(b"/");
|
||||
}
|
||||
_ => res.push(*c),
|
||||
}
|
||||
if let Unescaped(start) = state {
|
||||
fmt.write_str(unsafe { str::from_utf8_unchecked(&self.bytes[start..]) })?;
|
||||
}
|
||||
|
||||
String::from_utf8(res).unwrap()
|
||||
} else {
|
||||
s
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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>,
|
||||
|
Loading…
x
Reference in New Issue
Block a user