diff --git a/src/Scriban.Tests/TestLexer.cs b/src/Scriban.Tests/TestLexer.cs index 08cc668c..12169991 100644 --- a/src/Scriban.Tests/TestLexer.cs +++ b/src/Scriban.Tests/TestLexer.cs @@ -638,37 +638,37 @@ public void ParseStringInvalid() var lexer = new Lexer("{{ '\\u' }}"); var tokens = lexer.ToList(); Assert.True(lexer.HasErrors); - StringAssert.Contains("Unexpected escape character", lexer.Errors.First().Message); + StringAssert.Contains("Unexpected hex number", lexer.Errors.First().Message); } { var lexer = new Lexer("{{ '\\u1' }}"); var tokens = lexer.ToList(); Assert.True(lexer.HasErrors); - StringAssert.Contains("Unexpected escape character", lexer.Errors.First().Message); + StringAssert.Contains("Unexpected hex number", lexer.Errors.First().Message); } { var lexer = new Lexer("{{ '\\u12' }}"); var tokens = lexer.ToList(); Assert.True(lexer.HasErrors); - StringAssert.Contains("Unexpected escape character", lexer.Errors.First().Message); + StringAssert.Contains("Unexpected hex number", lexer.Errors.First().Message); } { var lexer = new Lexer("{{ '\\u123' }}"); var tokens = lexer.ToList(); Assert.True(lexer.HasErrors); - StringAssert.Contains("Unexpected escape character", lexer.Errors.First().Message); + StringAssert.Contains("Unexpected hex number", lexer.Errors.First().Message); } { var lexer = new Lexer("{{ '\\x' }}"); var tokens = lexer.ToList(); Assert.True(lexer.HasErrors); - StringAssert.Contains("Unexpected escape character", lexer.Errors.First().Message); + StringAssert.Contains("Unexpected hex number", lexer.Errors.First().Message); } { var lexer = new Lexer("{{ '\\x1' }}"); var tokens = lexer.ToList(); Assert.True(lexer.HasErrors); - StringAssert.Contains("Unexpected escape character", lexer.Errors.First().Message); + StringAssert.Contains("Unexpected hex number", lexer.Errors.First().Message); } { var lexer = new Lexer("{{ '"); diff --git a/src/Scriban.Tests/TestParser.cs b/src/Scriban.Tests/TestParser.cs index 2ee83027..fb8ea2e0 100644 --- a/src/Scriban.Tests/TestParser.cs +++ b/src/Scriban.Tests/TestParser.cs @@ -45,7 +45,7 @@ public void TestRoundtrip1() } [Test] - public void TestLiquidError() + public void TestLiquidMissingClosingBrace() { var template = Template.ParseLiquid("{%endunless"); Assert.True(template.HasErrors); @@ -53,6 +53,13 @@ public void TestLiquidError() Assert.AreEqual("(1,3) : error : Unable to find a pending `unless` for this `endunless`", template.Messages[0].ToString()); } + [Test] + public void TestLiquidInvalidStringEscape() + { + var template = Template.ParseLiquid(@"{%""\u"""); + Assert.True(template.HasErrors); + } + [Test] public void RoundtripFunction() { diff --git a/src/Scriban/Parsing/Lexer.cs b/src/Scriban/Parsing/Lexer.cs index 649d97d2..9e64d5a0 100644 --- a/src/Scriban/Parsing/Lexer.cs +++ b/src/Scriban/Parsing/Lexer.cs @@ -1299,6 +1299,7 @@ private void ReadString() } } } + AddError($"Unexpected hex number `{c}` following `\\u`. Expecting `\\u0000` to `\\uffff`.", _position, _position); break; case 'x': end = _position; @@ -1315,6 +1316,7 @@ private void ReadString() continue; } } + AddError($"Unexpected hex number `{c}` following `\\x`. Expecting `\\x00` to `\\xff`", _position, _position); break; } diff --git a/src/Scriban/Parsing/Parser.Terminals.cs b/src/Scriban/Parsing/Parser.Terminals.cs index 805702d0..ce02d5da 100644 --- a/src/Scriban/Parsing/Parser.Terminals.cs +++ b/src/Scriban/Parsing/Parser.Terminals.cs @@ -155,10 +155,12 @@ private ScriptLiteral ParseString() case 'u': { i++; - var value = (text[i++].HexToInt() << 12) + - (text[i++].HexToInt() << 8) + - (text[i++].HexToInt() << 4) + - text[i].HexToInt(); + int value = 0; + if (i < text.Length) value = text[i++].HexToInt(); + if (i < text.Length) value = (value << 4) | text[i++].HexToInt(); + if (i < text.Length) value = (value << 4) | text[i++].HexToInt(); + if (i < text.Length) value = (value << 4) | text[i].HexToInt(); + // Is it correct? builder.Append(ConvertFromUtf32(value)); break; @@ -166,8 +168,9 @@ private ScriptLiteral ParseString() case 'x': { i++; - var value = (text[i++].HexToInt() << 4) + - text[i++].HexToInt(); + int value = 0; + if (i < text.Length) value = text[i++].HexToInt(); + if (i < text.Length) value = (value << 4) | text[i].HexToInt(); builder.Append((char) value); break; } diff --git a/src/Scriban/Parsing/Util.cs b/src/Scriban/Parsing/Util.cs index 8ae6cd7b..60b24562 100644 --- a/src/Scriban/Parsing/Util.cs +++ b/src/Scriban/Parsing/Util.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexandre Mutel. All rights reserved. +// Copyright (c) Alexandre Mutel. All rights reserved. // Licensed under the BSD-Clause 2 license. // See license.txt file in the project root for full license information. using System; @@ -28,7 +28,8 @@ public static int HexToInt(this char c) { return c - 'A' + 10; } - throw new ArgumentOutOfRangeException(nameof(c), $"The character '{c}' is not an hexa [0a-fA-F] character"); + // Don't throw an exception as we are checking and logging an error if IsHex is false already + return 0; } } } \ No newline at end of file