Repository: sunng87/handlebars-rust (Rust)

Issue:
Invalid syntax  if (even an escaped) \ is the last character in quotes
When defining a string in a template, which contains a `\` as last character (even escaped as `"\\"`) the renderer fails with an `InvalidSyntax` error.

Not sure if this is an intended behaviour, but it definitly feels like a bug and is also not the case in other parsers (like handlebars.js). Sorry if there is already another topic describing this, I have'nt found it.

here is an example which reproduces the bug:

```rust
#[macro_use]
extern crate handlebars;

use std::collections::BTreeMap;
use handlebars::Handlebars;

handlebars_helper!(replace: |input: str, from: str, to: str| {
    input.replace(from, to)
});

// Error when having a \ as last character in a string
fn main() {
    let data = BTreeMap::<String, String>::new();
    let mut bars = Handlebars::new();
    bars.register_helper("replace", Box::new(replace));

    // Replace a / with a \

    // Works with "\\ " (if the character, before the quote is not a "\" )
    println!("{}", bars.render_template(r#"{{replace "some/path" "/" "\\ " }}"#, &data).unwrap()); // works
    println!("{}", bars.render_template(r#"{{replace "some/path" "/" "\\something else" }}"#, &data).unwrap()); // works

    // Fails otherwise
    println!("{}", bars.render_template(r#"{{replace "some/path" "/" "\\" }}"#, &data).unwrap()); // fails
    println!("{}", bars.render_template(r#"{{replace "some/path" "/" "something before \\" }}"#, &data).unwrap()); // fails
}
```


Discussion:
These seems to be affected by how we deal with escape in json. It works with [the original javascript version](https://handlebarsjs.com/playground.html#format=1&currentExample=%7B%22template%22%3A%22%3Cp%3E%7B%7Bfirstname%7D%7D%20%7B%7Blastname%7D%7D%20%7B%7Bloud%20%5C%22yes%20%5C%5C%5C%22%7D%7D%3C%2Fp%3E%20%5Cn%22%2C%22partials%22%3A%5B%5D%2C%22input%22%3A%22%7B%5Cn%20%20firstname%3A%20%5C%22Yehuda%5C%22%2C%5Cn%20%20lastname%3A%20%5C%22Katz%5C%22%2C%5Cn%7D%5Cn%22%2C%22output%22%3A%22%3Cp%3EYehuda%20Katz%20YES%20%5C%5C%3C%2Fp%3E%20%5Cn%22%2C%22preparationScript%22%3A%22Handlebars.registerHelper('loud'%2C%20function(string)%20%7B%5Cn%20%20%20%20return%20string.toUpperCase()%5Cn%20%7D)%3B%5Cn%22%2C%22handlebarsVersion%22%3A%224.7.7%22%7D). Let me investigate.


@YannikSc the fix is available in 3.5.4



Patch:
diff --git a/src/grammar.pest b/src/grammar.pest
index 9f87fae94..6ed45e53f 100644
--- a/src/grammar.pest
+++ b/src/grammar.pest
@@ -15,7 +15,13 @@ literal = { string_literal |
 null_literal = @{ "null" ~ !symbol_char }
 boolean_literal = @{ ("true"|"false") ~ !symbol_char }
 number_literal = @{ "-"? ~ ASCII_DIGIT+ ~ "."? ~ ASCII_DIGIT* ~ ("E" ~ "-"? ~ ASCII_DIGIT+)? }
-string_literal = @{ ("\"" ~ (!"\"" ~ ("\\\"" | ANY))* ~ "\"") | ("'" ~ (!"'" ~ ("\\'" | ANY))* ~ "'") }
+json_char = {
+    !("\"" | "\\") ~ ANY
+    | "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
+    | "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
+}
+string_inner = @{ json_char* }
+string_literal = ${ "\"" ~ string_inner ~ "\"" }
 array_literal = { "[" ~ literal? ~ ("," ~ literal)* ~ "]" }
 object_literal = { "{" ~ (string_literal ~ ":" ~ literal)?
                    ~ ("," ~ string_literal ~ ":" ~ literal)* ~ "}" }
diff --git a/src/grammar.rs b/src/grammar.rs
index 6f7b23803..6be52a6cc 100644
--- a/src/grammar.rs
+++ b/src/grammar.rs
@@ -125,8 +125,6 @@ mod test {
         let s = vec![
             "\"json string\"",
             "\"quot: \\\"\"",
-            "'json string'",
-            "'quot: \\''",
             "[]",
             "[\"hello\"]",
             "[1,2,3,4,true]",
@@ -174,6 +172,7 @@ mod test {
             "{{exp 1}}",
             "{{exp \"literal\"}}",
             "{{exp \"literal with space\"}}",
+            r#"{{exp "literal with escape \\\\"}}"#,
             "{{exp ref}}",
             "{{exp (sub)}}",
             "{{exp (sub 123)}}",
diff --git a/src/lib.rs b/src/lib.rs
index 1bf311666..4ba1a3dea 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -358,7 +358,7 @@
 //!
 //!
 
-#![allow(dead_code)]
+#![allow(dead_code, clippy::upper_case_acronyms)]
 #![warn(rust_2018_idioms)]
 #![recursion_limit = "200"]
 


Test patch:
diff --git a/tests/escape.rs b/tests/escape.rs
index 360a74fc5..563ab76b9 100644
--- a/tests/escape.rs
+++ b/tests/escape.rs
@@ -3,7 +3,7 @@ extern crate handlebars;
 #[macro_use]
 extern crate serde_json;
 
-use handlebars::Handlebars;
+use handlebars::{handlebars_helper, Handlebars};
 
 #[test]
 fn test_escape_216() {
@@ -19,3 +19,25 @@ fn test_escape_216() {
         r"\\\\ foo bar foobar foo#bar foo//bar foo\foo foo\\\foo\\foo \\foo {{FOO}} {{FOO}}"
     );
 }
+
+#[test]
+fn test_string_no_escape_422() {
+    let mut hbs = Handlebars::new();
+
+    handlebars_helper!(replace: |input: str, from: str, to: str| {
+        input.replace(from, to)
+    });
+    hbs.register_helper("replace", Box::new(replace));
+
+    assert_eq!(
+        r#"some\ path"#,
+        hbs.render_template(r#"{{replace "some/path" "/" "\\ " }}"#, &())
+            .unwrap()
+    );
+
+    assert_eq!(
+        r#"some\path"#,
+        hbs.render_template(r#"{{replace "some/path" "/" "\\" }}"#, &())
+            .unwrap()
+    );
+}