From 74d68b245f1c5279884433641e568c37763d331c Mon Sep 17 00:00:00 2001 From: Rex Morgan Date: Fri, 19 Jun 2026 20:55:27 -0400 Subject: [PATCH] fix: handle escaped double-quotes in delimited string literal arguments (issue #584) The LiteralParser's AccumulateLiteral method now correctly processes backslash escape sequences inside delimited string literals, so that \" inside a double-quoted helper argument is treated as a literal quote rather than terminating the string. Adds two regression tests for issue #584. Co-Authored-By: Claude Sonnet 4.6 --- source/Handlebars.Test/IssueTests.cs | 29 +++++++++++++++++++ .../Compiler/Lexer/Parsers/LiteralParser.cs | 24 ++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/source/Handlebars.Test/IssueTests.cs b/source/Handlebars.Test/IssueTests.cs index 31136117..08f97e75 100644 --- a/source/Handlebars.Test/IssueTests.cs +++ b/source/Handlebars.Test/IssueTests.cs @@ -733,5 +733,34 @@ public void UnrecognisedExpressionThrowsOutOfMemoryException() Assert.Throws(()=> Handlebars.Compile(source)); } + + // Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/584 + [Fact] + public void Issue584_EscapedDoubleQuoteInHelperStringArgument() + { + var handlebars = Handlebars.Create(); + handlebars.RegisterHelper("myHelper", (writer, context, args) => + { + writer.WriteSafeString(args[0]?.ToString() + "|" + args[1]?.ToString()); + }); + + // Double-quoted string arg with escaped double-quote inside + var template = handlebars.Compile("{{myHelper name \"hello \\\"world\\\"\"}}"); + var data = new { name = "test" }; + var result = template(data); + Assert.Equal("test|hello \"world\"", result); + } + + // Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/584 + [Fact] + public void Issue584_SingleQuoteStringArgStillWorks() + { + var handlebars = Handlebars.Create(); + handlebars.RegisterHelper("myHelper", (context, args) => args[0]?.ToString()); + + var template = handlebars.Compile("{{myHelper 'hello world'}}"); + var result = template(new { }); + Assert.Equal("hello world", result); + } } } \ No newline at end of file diff --git a/source/Handlebars/Compiler/Lexer/Parsers/LiteralParser.cs b/source/Handlebars/Compiler/Lexer/Parsers/LiteralParser.cs index 822aac90..e4b1a340 100644 --- a/source/Handlebars/Compiler/Lexer/Parsers/LiteralParser.cs +++ b/source/Handlebars/Compiler/Lexer/Parsers/LiteralParser.cs @@ -49,6 +49,28 @@ private static string AccumulateLiteral(ExtendedStringReader reader, bool captur throw new HandlebarsParserException("Reached end of template before the expression was closed.", reader.GetContext()); } + // Handle escape sequences inside delimited literals (e.g. \" inside "...") + if (captureDelimiter && (char)node == '\\') + { + reader.Read(); // consume the backslash + var next = reader.Peek(); + if (next == -1) + { + throw new HandlebarsParserException("Reached end of template before the expression was closed.", reader.GetContext()); + } + var nextChar = (char)next; + // If the escaped character is one of the delimiters, emit the raw char + if (delimiters.Contains(nextChar)) + { + reader.Read(); + buffer.Append(nextChar); + continue; + } + // Otherwise keep the backslash and let the next iteration handle the char + buffer.Append('\\'); + continue; + } + if (delimiters.Contains((char)node)) { if (captureDelimiter) @@ -65,7 +87,7 @@ private static string AccumulateLiteral(ExtendedStringReader reader, bool captur buffer.Append((char)reader.Read()); } - + return buffer.ToString(); } }