diff --git a/Source/HtmlRenderer/Core/CssEngine/Extensions/CharExtensions.cs b/Source/HtmlRenderer/Core/CssEngine/Extensions/CharExtensions.cs
index 26ef23e5b..b2054ec40 100644
--- a/Source/HtmlRenderer/Core/CssEngine/Extensions/CharExtensions.cs
+++ b/Source/HtmlRenderer/Core/CssEngine/Extensions/CharExtensions.cs
@@ -109,7 +109,7 @@ public static bool IsDigit(this char c)
public static bool IsInvalid(this int c)
{
return c == 0 || c > Symbols.MaximumCodepoint ||
- (c > Symbols.UTF16SurrogateMin && c < Symbols.UTF16SurrogateMax);
+ (c >= Symbols.UTF16SurrogateMin && c <= Symbols.UTF16SurrogateMax);
}
public static bool IsOneOf(this char c, char a, char b)
diff --git a/Source/Test/HtmlRenderer.Test/Css/TokenizationTests.cs b/Source/Test/HtmlRenderer.Test/Css/TokenizationTests.cs
index 4a1b323c2..e9a444573 100644
--- a/Source/Test/HtmlRenderer.Test/Css/TokenizationTests.cs
+++ b/Source/Test/HtmlRenderer.Test/Css/TokenizationTests.cs
@@ -152,6 +152,32 @@ static void Check(string input, TokenType expectedType, string expectedData)
Assert.AreEqual(TokenType.Delim, delim.Get().Type);
}
+ // CSS Syntax 3 §4.3.7: zero, a surrogate or a code point above U+10FFFF becomes U+FFFD
+ [TestMethod]
+ [DataRow(@"'\0'", "�")]
+ [DataRow(@"'\D800'", "�")]
+ [DataRow(@"'\DABC'", "�")]
+ [DataRow(@"'\DFFF'", "�")]
+ [DataRow(@"'\110000'", "�")]
+ [DataRow(@"'\D7FF\E000'", "")]
+ [DataRow(@"'\10FFFF'", "")]
+ public void LexerInvalidEscapedCodePointInString(string input, string expected)
+ {
+ var token = new Lexer(new TextSource(input)).Get();
+ Assert.AreEqual(TokenType.String, token.Type);
+ Assert.AreEqual(expected, token.Data);
+ }
+
+ [TestMethod]
+ [DataRow(@"a\D800 b")]
+ [DataRow(@"a\DFFF b")]
+ public void LexerInvalidEscapedCodePointInIdent(string input)
+ {
+ var token = new Lexer(new TextSource(input)).Get();
+ Assert.AreEqual(TokenType.Ident, token.Type);
+ Assert.AreEqual("a�b", token.Data);
+ }
+
[TestMethod]
public void LexerOnlyCarriageReturn()
{