Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions source/Handlebars.Test/IssueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,34 @@ public void UnrecognisedExpressionThrowsOutOfMemoryException()
Assert.Throws<HandlebarsCompilerException>(()=> Handlebars.Compile(source));
}

// Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/605
// #if not evaluated when variable name contains invisible characters (BOM)
[Fact]
public void Issue605_IfNotEvaluatedWithBomCharacter()
{
// The  (BOM) is embedded in the identifier name
string source =
"<div class=\"entry\">\n" +
"<h1>{{title}}</h1>\n" +
"<div class=\"body\">\n" +
"{{body}}\n" +
"{{#if someCondition}}\n" +
"<p>Show additional text</p>\n" +
"{{/if}}\n" +
"</div>\n" +
"</div>";

var handlebars = Handlebars.Create();
var template = handlebars.Compile(source);
var data = new {
title = "My new post",
body = "This is my first post!",
someCondition = true
};
var actual = template(data);
Assert.Contains("Show additional text", actual);
}

// Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/546
// Single quote should be HTML-encoded as &#x27; in standard {{expression}} output
[Fact]
Expand Down
31 changes: 26 additions & 5 deletions source/Handlebars/Compiler/Lexer/Parsers/WordParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ internal class WordParser : Parser
private const string ValidWordStartCharactersString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$.@[]*";
private static readonly HashSet<char> ValidWordStartCharacters = new HashSet<char>();

// Invisible Unicode characters that should be stripped from identifiers
// Includes BOM (U+FEFF), zero-width space (U+200B), zero-width non-joiner (U+200C),
// zero-width joiner (U+200D), word joiner (U+2060), and other format characters.
private static bool IsInvisibleCharacter(char c)
{
return c == '' // BOM / Zero Width No-Break Space
|| c == '​' // Zero Width Space
|| c == '‌' // Zero Width Non-Joiner
|| c == '‍' // Zero Width Joiner
|| c == '⁠' // Word Joiner
|| c == '᠎' // Mongolian Vowel Separator
|| char.GetUnicodeCategory(c) == System.Globalization.UnicodeCategory.Format;
}

static WordParser()
{
for (var index = 0; index < ValidWordStartCharactersString.Length; index++)
Expand All @@ -23,7 +37,7 @@ public override Token Parse(ExtendedStringReader reader)
{
var context = reader.GetContext();
if (!IsWord(reader)) return null;

var buffer = AccumulateWord(reader);
return Token.Word(buffer, context);
}
Expand All @@ -38,7 +52,7 @@ private static string AccumulateWord(ExtendedStringReader reader)
{
using var container = StringBuilderPool.Shared.Use();
var buffer = container.Value;

var inString = false;
var isEscaped = false;

Expand All @@ -65,7 +79,7 @@ private static string AccumulateWord(ExtendedStringReader reader)
{
var c = (char) node;
if (c == ']') isEscaped = false;

buffer.Append(c);
continue;
}
Expand All @@ -76,15 +90,22 @@ private static string AccumulateWord(ExtendedStringReader reader)
buffer.Append((char)node);
continue;
}

if (node == '\'' || node == '"')
{
inString = !inString;
}

// Skip invisible Unicode characters (BOM, zero-width chars, etc.)
// that can appear in identifiers due to editor/encoding artifacts
if (!inString && IsInvisibleCharacter((char) node))
{
continue;
}

buffer.Append((char)node);
}

return buffer.Trim().ToString();
}
}
Expand Down
Loading