Repository: djc/askama
Pull request #287 (2020-01-11 23:00:08)

Support escaping in string literals
Do not attempt to parse escape sequences thoroughly. Instead let the
Rust compiler to check the string literals and provide nice error
messages if necessary.

djc: Nice, thanks! 

Patch:
diff --git a/askama_derive/src/parser.rs b/askama_derive/src/parser.rs
index 91c2d6a..910815a 100644
--- a/askama_derive/src/parser.rs
+++ b/askama_derive/src/parser.rs
@@ -1,6 +1,6 @@
 use nom::branch::alt;
-use nom::bytes::complete::{is_not, tag, take_until};
-use nom::character::complete::{char, digit1};
+use nom::bytes::complete::{escaped, is_not, tag, take_until};
+use nom::character::complete::{anychar, char, digit1};
 use nom::combinator::{complete, map, opt};
 use nom::error::ParseError;
 use nom::multi::{many0, many1, separated_list, separated_nonempty_list};
@@ -251,40 +251,50 @@ fn param_num_lit(i: &[u8]) -> IResult<&[u8], MatchParameter> {
     map(num_lit, |s| MatchParameter::NumLit(s))(i)
 }
 
+fn str_lit(i: &[u8]) -> IResult<&[u8], &str> {
+    map(
+        delimited(
+            char('\"'),
+            opt(escaped(is_not("\\\""), '\\', anychar)),
+            char('\"'),
+        ),
+        |s| s.map(|s| str::from_utf8(s).unwrap()).unwrap_or(""),
+    )(i)
+}
+
 fn expr_str_lit(i: &[u8]) -> IResult<&[u8], Expr> {
-    map(delimited(char('"'), take_until("\""), char('"')), |s| {
-        Expr::StrLit(str::from_utf8(s).unwrap())
-    })(i)
+    map(str_lit, |s| Expr::StrLit(s))(i)
 }
 
 fn variant_str_lit(i: &[u8]) -> IResult<&[u8], MatchVariant> {
-    map(delimited(char('"'), is_not("\""), char('"')), |s| {
-        MatchVariant::StrLit(str::from_utf8(s).unwrap())
-    })(i)
+    map(str_lit, |s| MatchVariant::StrLit(s))(i)
 }
 
 fn param_str_lit(i: &[u8]) -> IResult<&[u8], MatchParameter> {
-    map(delimited(char('"'), is_not("\""), char('"')), |s| {
-        MatchParameter::StrLit(str::from_utf8(s).unwrap())
-    })(i)
+    map(str_lit, |s| MatchParameter::StrLit(s))(i)
+}
+
+fn char_lit(i: &[u8]) -> IResult<&[u8], &str> {
+    map(
+        delimited(
+            char('\''),
+            opt(escaped(is_not("\\\'"), '\\', anychar)),
+            char('\''),
+        ),
+        |s| s.map(|s| str::from_utf8(s).unwrap()).unwrap_or(""),
+    )(i)
 }
 
 fn expr_char_lit(i: &[u8]) -> IResult<&[u8], Expr> {
-    map(delimited(char('\''), take_until("'"), char('\'')), |s| {
-        Expr::CharLit(str::from_utf8(s).unwrap())
-    })(i)
+    map(char_lit, |s| Expr::CharLit(s))(i)
 }
 
 fn variant_char_lit(i: &[u8]) -> IResult<&[u8], MatchVariant> {
-    map(delimited(char('\''), is_not("'"), char('\'')), |s| {
-        MatchVariant::CharLit(str::from_utf8(s).unwrap())
-    })(i)
+    map(char_lit, |s| MatchVariant::CharLit(s))(i)
 }
 
 fn param_char_lit(i: &[u8]) -> IResult<&[u8], MatchParameter> {
-    map(delimited(char('\''), is_not("'"), char('\'')), |s| {
-        MatchParameter::CharLit(str::from_utf8(s).unwrap())
-    })(i)
+    map(char_lit, |s| MatchParameter::CharLit(s))(i)
 }
 
 fn expr_var(i: &[u8]) -> IResult<&[u8], Expr> {


Test patch:
diff --git a/testing/templates/literals-escape.html b/testing/templates/literals-escape.html
new file mode 100644
index 0000000..4886cfb
--- /dev/null
+++ b/testing/templates/literals-escape.html
@@ -0,0 +1,2 @@
+{{ '\x41' }}{{ '\n' }}{{ '\r' }}{{ '\t' }}{{ '\\' }}{{ '\0' }}{{ '\u{2665}' }}{{ '\'' }}{{ '\"' }}{{ '"' }}
+{{ "\x41\n\r\t\\\0\u{2665}\'\"'" }}
diff --git a/testing/tests/simple.rs b/testing/tests/simple.rs
index 53f1122..a075d26 100644
--- a/testing/tests/simple.rs
+++ b/testing/tests/simple.rs
@@ -122,6 +122,19 @@ fn test_literals() {
     assert_eq!(s.render().unwrap(), "a\na\ntrue\nfalse");
 }
 
+#[derive(Template)]
+#[template(path = "literals-escape.html")]
+struct LiteralsEscapeTemplate {}
+
+#[test]
+fn test_literals_escape() {
+    let s = LiteralsEscapeTemplate {};
+    assert_eq!(
+        s.render().unwrap(),
+        "A\n\r\t\\\0♥&#x27;&quot;&quot;\nA\n\r\t\\\0♥&#x27;&quot;&#x27;"
+    );
+}
+
 struct Holder {
     a: usize,
 }