From ec5c6db664189ffc3370ee8ead3a5c86da056ad9 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 6 Oct 2021 17:58:15 +0200 Subject: [PATCH 1/3] Fix StringParser --- src-console/ConsoleAppEF2.1.1/Program.cs | 8 ++++ .../Parser/StringParser.cs | 46 +++++++++---------- .../DynamicExpressionParserTests.cs | 14 ++++++ .../Parser/StringParserTests.cs | 7 ++- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src-console/ConsoleAppEF2.1.1/Program.cs b/src-console/ConsoleAppEF2.1.1/Program.cs index 404b9b82..e560635d 100644 --- a/src-console/ConsoleAppEF2.1.1/Program.cs +++ b/src-console/ConsoleAppEF2.1.1/Program.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core.CustomTypeProviders; +using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using ConsoleAppEF2.Database; @@ -74,6 +75,13 @@ private static IQueryable GetQueryable() static void Main(string[] args) { + var value = "\"\\\\192.168.1.1\\audio\\new\""; + var dataParameter = Expression.Parameter(typeof(Dictionary), "data"); + var sourceExpr = DynamicExpressionParser.ParseLambda(new[] { dataParameter }, typeof(object), value); + var func = sourceExpr.Compile(); + var resultX = func.DynamicInvoke(new Dictionary()); + Console.WriteLine(resultX); + IQueryable qry = GetQueryable(); var result = qry.Select("it").OrderBy("Value"); diff --git a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs index f17d2b3d..05870aa7 100644 --- a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs @@ -15,7 +15,7 @@ public static string ParseString(string s) { var inputStringBuilder = new StringBuilder(s); var tempStringBuilder = new StringBuilder(); - string found = null; + //string found = null; char quote = inputStringBuilder[0]; int pos = 1; @@ -42,8 +42,8 @@ public static string ParseString(string s) } else if (ch == quote) { - found = Replace(tempStringBuilder); - break; + // End double-quote found, return. + return tempStringBuilder.ToString(); } else { @@ -53,29 +53,29 @@ public static string ParseString(string s) pos++; } - if (found == null) - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, pos, inputStringBuilder.ToString()), pos); - } + //if (found == null) + //{ + // throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, pos, inputStringBuilder.ToString()), pos); + //} - return found; + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, pos, inputStringBuilder.ToString()), pos); } - private static string Replace(StringBuilder inputStringBuilder) - { - var sb = new StringBuilder(inputStringBuilder.ToString()) - .Replace(@"\\", "\\") // \\ – backslash - .Replace(@"\0", "\0") // Unicode character 0 - .Replace(@"\a", "\a") // Alert(character 7) - .Replace(@"\b", "\b") // Backspace(character 8) - .Replace(@"\f", "\f") // Form feed(character 12) - .Replace(@"\n", "\n") // New line(character 10) - .Replace(@"\r", "\r") // Carriage return (character 13) - .Replace(@"\t", "\t") // Horizontal tab(character 9) - .Replace(@"\v", "\v") // Vertical quote(character 11) - ; + //private static string Replace(StringBuilder inputStringBuilder) + //{ + // var sb = new StringBuilder(inputStringBuilder.ToString()) + // .Replace(@"\\", "\\") // \\ – backslash + // .Replace(@"\0", "\0") // Unicode character 0 + // .Replace(@"\a", "\a") // Alert(character 7) + // .Replace(@"\b", "\b") // Backspace(character 8) + // .Replace(@"\f", "\f") // Form feed(character 12) + // .Replace(@"\n", "\n") // New line(character 10) + // .Replace(@"\r", "\r") // Carriage return (character 13) + // .Replace(@"\t", "\t") // Horizontal tab(character 9) + // .Replace(@"\v", "\v") // Vertical quote(character 11) + // ; - return sb.ToString(); - } + // return sb.ToString(); + //} } } diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 973293ef..fea1473b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -866,6 +866,20 @@ public void DynamicExpressionParser_ParseLambda_StringLiteralEscapedBackslash_Re Assert.Equal("\"test\\string\"", rightValue); } + [Fact] + public void DynamicExpressionParser_ParseLambda_StringLiteralEscapedNewline_ReturnsBooleanLambdaExpression() + { + // Act + var expression = DynamicExpressionParser.ParseLambda( + new[] { Expression.Parameter(typeof(string), "Property1") }, + typeof(bool), + string.Format("Property1 == {0}", "\"test\\\\\\new\"")); + + string rightValue = ((BinaryExpression)expression.Body).Right.ToString(); + Assert.Equal(typeof(Boolean), expression.Body.Type); + Assert.Equal("\"test\\\\new\"", rightValue); + } + [Fact] public void DynamicExpressionParser_ParseLambda_StringLiteral_Backslash() { diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs index 177a388e..9141a06b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs @@ -34,7 +34,8 @@ public void StringParser_With_UnexpectedUnrecognizedEscapeSequence_ThrowsExcepti [Theory] [InlineData("'s'", "s")] - [InlineData("'\\r'", "\r")] + [InlineData("'\\r'", "\\r")] + [InlineData("'\\\r'", "\\\r")] [InlineData("'\\\\'", "\\")] public void StringParser_Parse_SingleQuotedString(string input, string expectedResult) { @@ -46,6 +47,9 @@ public void StringParser_Parse_SingleQuotedString(string input, string expectedR } [Theory] + [InlineData("\"\\\\\"", "\\")] + [InlineData("\"\n\"", "\n")] + [InlineData("\"\\\\new\"", "\\new")] [InlineData("\"\"", "")] [InlineData("\"[]\"", "[]")] [InlineData("\"()\"", "()")] @@ -59,6 +63,7 @@ public void StringParser_Parse_SingleQuotedString(string input, string expectedR [InlineData("\"\\\"\\\"\"", "\"\"")] [InlineData("\"\\\\\"", "\\")] [InlineData("\"AB YZ 19 \uD800\udc05 \u00e4\"", "AB YZ 19 \uD800\udc05 \u00e4")] + [InlineData("\"\\\\192.168.1.1\\audio\\new\"", "\\192.168.1.1\\audio\\new")] public void StringParser_Parse_DoubleQuotedString(string input, string expectedResult) { // Act From 7bab31c3eff6b7b96d9d026505953627b0c43c6e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 7 Oct 2021 15:14:34 +0000 Subject: [PATCH 2/3] Regex.Unescape --- .../Parser/StringParser.cs | 78 +++++-------------- src/System.Linq.Dynamic.Core/Res.cs | 2 + .../System.Linq.Dynamic.Core.csproj | 5 +- .../DynamicExpressionParserTests.cs | 4 +- .../Parser/StringParserTests.cs | 43 +++++++--- 5 files changed, 60 insertions(+), 72 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs index 05870aa7..f74283b6 100644 --- a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs @@ -1,6 +1,5 @@ using System.Globalization; using System.Linq.Dynamic.Core.Exceptions; -using System.Text; using System.Text.RegularExpressions; namespace System.Linq.Dynamic.Core.Parser @@ -13,69 +12,30 @@ internal static class StringParser { public static string ParseString(string s) { - var inputStringBuilder = new StringBuilder(s); - var tempStringBuilder = new StringBuilder(); - //string found = null; - - char quote = inputStringBuilder[0]; - int pos = 1; - - while (pos < inputStringBuilder.Length) + if (s == null || s.Length < 2) { - char ch = inputStringBuilder[pos]; - - if (ch == '\\' && pos + 1 < inputStringBuilder.Length && (inputStringBuilder[pos + 1] == '\\' || inputStringBuilder[pos + 1] == quote)) - { - tempStringBuilder.Append(inputStringBuilder[pos + 1]); - pos++; // Treat as escape character for \\ or \' - } - else if (ch == '\\' && pos + 1 < inputStringBuilder.Length && inputStringBuilder[pos + 1] == 'u') - { - if (pos + 5 >= inputStringBuilder.Length) - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnrecognizedEscapeSequence, pos, inputStringBuilder.ToString(pos, inputStringBuilder.Length - pos - 1)), pos); - } - - string unicode = inputStringBuilder.ToString(pos, 6); - tempStringBuilder.Append(Regex.Unescape(unicode)); - pos += 5; - } - else if (ch == quote) - { - // End double-quote found, return. - return tempStringBuilder.ToString(); - } - else - { - tempStringBuilder.Append(ch); - } + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringLength, s, 2), 0); + } - pos++; + if (s[0] != '"' && s[0] != '\'') + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringQuoteCharacter), 0); } - //if (found == null) - //{ - // throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, pos, inputStringBuilder.ToString()), pos); - //} + char quote = s[0]; // This can be single or a double quote + if (s.Last() != quote) + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, s.Length, s), s.Length); + } - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, pos, inputStringBuilder.ToString()), pos); + try + { + return Regex.Unescape(s.Substring(1, s.Length - 2)); + } + catch (Exception ex) + { + throw new ParseException(ex.Message, 0); + } } - - //private static string Replace(StringBuilder inputStringBuilder) - //{ - // var sb = new StringBuilder(inputStringBuilder.ToString()) - // .Replace(@"\\", "\\") // \\ – backslash - // .Replace(@"\0", "\0") // Unicode character 0 - // .Replace(@"\a", "\a") // Alert(character 7) - // .Replace(@"\b", "\b") // Backspace(character 8) - // .Replace(@"\f", "\f") // Form feed(character 12) - // .Replace(@"\n", "\n") // New line(character 10) - // .Replace(@"\r", "\r") // Carriage return (character 13) - // .Replace(@"\t", "\t") // Horizontal tab(character 9) - // .Replace(@"\v", "\v") // Vertical quote(character 11) - // ; - - // return sb.ToString(); - //} } } diff --git a/src/System.Linq.Dynamic.Core/Res.cs b/src/System.Linq.Dynamic.Core/Res.cs index d6b0e377..73c44669 100644 --- a/src/System.Linq.Dynamic.Core/Res.cs +++ b/src/System.Linq.Dynamic.Core/Res.cs @@ -43,6 +43,8 @@ internal static class Res public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'"; public const string InvalidIntegerQualifier = "Invalid integer literal qualifier '{0}'"; public const string InvalidRealLiteral = "Invalid real literal '{0}'"; + public const string InvalidStringQuoteCharacter = "An escaped string should start with a double (\") or a single (') quote."; + public const string InvalidStringLength = "String '{0}' should have at least {1} characters."; public const string IsNullRequiresTwoArgs = "The 'isnull' function requires two arguments"; public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value"; public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible"; diff --git a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj index 46822fec..a36aa35b 100644 --- a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj +++ b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj @@ -6,7 +6,8 @@ This is a .NETStandard / .NET Core port of the the Microsoft assembly for the .Net 4.0 Dynamic language functionality. System.Linq.Dynamic.Core ZZZ Projects;Stef Heyenrath;Microsoft;Scott Guthrie;King Wilder;Nathan Arnott - net35;net40;net45;net46;netstandard1.3;netstandard2.0;netstandard2.1;uap10.0;netcoreapp2.1;net5.0 + net35;net40;net45;net46;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp2.1;net5.0 + true System.Linq.Dynamic.Core System.Linq.Dynamic.Core.snk @@ -47,7 +48,7 @@ false UAP,Version=v10.0 UAP - 10.0.18362.0 + 10.0.20348.0 10.0.10240.0 .NETCore v5.0 diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index fea1473b..f023c262 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -873,11 +873,11 @@ public void DynamicExpressionParser_ParseLambda_StringLiteralEscapedNewline_Retu var expression = DynamicExpressionParser.ParseLambda( new[] { Expression.Parameter(typeof(string), "Property1") }, typeof(bool), - string.Format("Property1 == {0}", "\"test\\\\\\new\"")); + string.Format("Property1 == {0}", "\"test\\\\new\"")); string rightValue = ((BinaryExpression)expression.Body).Right.ToString(); Assert.Equal(typeof(Boolean), expression.Body.Type); - Assert.Equal("\"test\\\\new\"", rightValue); + Assert.Equal("\"test\\new\"", rightValue); } [Fact] diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs index 9141a06b..91000885 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs @@ -10,7 +10,7 @@ public class StringParserTests [Theory] [InlineData("'s")] [InlineData("\"s")] - public void StringParser_WithUnexpectedUnclosedString_ThrowsException(string input) + public void StringParser_With_UnexpectedUnclosedString_ThrowsException(string input) { // Act var exception = Assert.Throws(() => StringParser.ParseString(input)); @@ -19,6 +19,31 @@ public void StringParser_WithUnexpectedUnclosedString_ThrowsException(string inp Assert.Equal($"Unexpected end of string with unclosed string at position 2 near '{input}'.", exception.Message); } + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("x")] + public void StringParser_With_InvalidStringLength_ThrowsException(string input) + { + // Act + Action action = () => StringParser.ParseString(input); + + // Assert + action.Should().Throw().WithMessage($"String '{input}' should have at least 2 characters."); + } + + [Theory] + [InlineData("xx")] + [InlineData(" ")] + public void StringParser_With_InvalidStringQuoteCharacter_ThrowsException(string input) + { + // Act + Action action = () => StringParser.ParseString(input); + + // Assert + action.Should().Throw().WithMessage("An escaped string should start with a double (\") or a single (') quote."); + } + [Fact] public void StringParser_With_UnexpectedUnrecognizedEscapeSequence_ThrowsException() { @@ -26,17 +51,17 @@ public void StringParser_With_UnexpectedUnrecognizedEscapeSequence_ThrowsExcepti string input = new string(new[] { '"', '\\', 'u', '?', '"' }); // Act - var exception = Assert.Throws(() => StringParser.ParseString(input)); + Action action = () => StringParser.ParseString(input); // Assert - Assert.Equal("Unexpected unrecognized escape sequence at position 1 near '\\u?'.", exception.Message); + action.Should().Throw(); } [Theory] + [InlineData("''", "")] [InlineData("'s'", "s")] - [InlineData("'\\r'", "\\r")] - [InlineData("'\\\r'", "\\\r")] [InlineData("'\\\\'", "\\")] + [InlineData("'\\n'", "\n")] public void StringParser_Parse_SingleQuotedString(string input, string expectedResult) { // Act @@ -47,10 +72,11 @@ public void StringParser_Parse_SingleQuotedString(string input, string expectedR } [Theory] + [InlineData("\"\"", "")] [InlineData("\"\\\\\"", "\\")] - [InlineData("\"\n\"", "\n")] + [InlineData("\"\\n\"", "\n")] + [InlineData("\"\\\\n\"", "\\n")] [InlineData("\"\\\\new\"", "\\new")] - [InlineData("\"\"", "")] [InlineData("\"[]\"", "[]")] [InlineData("\"()\"", "()")] [InlineData("\"(\\\"\\\")\"", "(\"\")")] @@ -61,9 +87,8 @@ public void StringParser_Parse_SingleQuotedString(string input, string expectedR [InlineData("\"ab\\\"cd\"", "ab\"cd")] [InlineData("\"\\\"\"", "\"")] [InlineData("\"\\\"\\\"\"", "\"\"")] - [InlineData("\"\\\\\"", "\\")] [InlineData("\"AB YZ 19 \uD800\udc05 \u00e4\"", "AB YZ 19 \uD800\udc05 \u00e4")] - [InlineData("\"\\\\192.168.1.1\\audio\\new\"", "\\192.168.1.1\\audio\\new")] + [InlineData("\"\\\\\\\\192.168.1.1\\\\audio\\\\new\"", "\\\\192.168.1.1\\audio\\new")] public void StringParser_Parse_DoubleQuotedString(string input, string expectedResult) { // Act From e431050cedae92f719b3ad36b29f455f097b383a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Fri, 8 Oct 2021 14:47:18 +0200 Subject: [PATCH 3/3] TargetFrameworks --- src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj index a36aa35b..0a807acc 100644 --- a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj +++ b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj @@ -6,8 +6,8 @@ This is a .NETStandard / .NET Core port of the the Microsoft assembly for the .Net 4.0 Dynamic language functionality. System.Linq.Dynamic.Core ZZZ Projects;Stef Heyenrath;Microsoft;Scott Guthrie;King Wilder;Nathan Arnott - net35;net40;net45;net46;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp2.1;net5.0 - + + net35;net40;net45;net46;netstandard1.3;netstandard2.0;netstandard2.1;uap10.0;netcoreapp2.1;net5.0 true System.Linq.Dynamic.Core System.Linq.Dynamic.Core.snk