ASSERTION FAILED: character != kEndOfFileMarker in WebCore::HTMLTokenizer::bufferChar...
authordarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 8 Jan 2015 21:12:08 +0000 (21:12 +0000)
committerdarin@apple.com <darin@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 8 Jan 2015 21:12:08 +0000 (21:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=140179

Reviewed by Anders Carlsson.

Source/WebCore:

Test: fast/parser/numeric-entities.html

* html/parser/HTMLEntityParser.cpp:
(WebCore::HTMLEntityParser::legalEntityFor): Merged adjustEntity logic in here.
Since the type UChar32 is a signed integer, need to check for <= 0, not just 0.
This <= change alone would have fixed the bug.

* xml/parser/CharacterReferenceParserInlines.h:
(WebCore::consumeCharacterReference): Added overflow checking when parsing hex
and decimal character references. This change alone would also have fixed the
bug, but in addition it makes overflow cases reliably generate replacement
characters rather than ignoring the overflow and producing seemingly random
characters. Test cases cover the original reported bug and other overflow cases.

LayoutTests:

* fast/parser/numeric-entities-expected.txt: Added.
* fast/parser/numeric-entities.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@178128 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/parser/numeric-entities-expected.txt [new file with mode: 0644]
LayoutTests/fast/parser/numeric-entities.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/html/parser/HTMLEntityParser.cpp
Source/WebCore/xml/parser/CharacterReferenceParserInlines.h

index 9f8096e..037d64c 100644 (file)
@@ -1,3 +1,13 @@
+2015-01-08  Darin Adler  <darin@apple.com>
+
+        ASSERTION FAILED: character != kEndOfFileMarker in WebCore::HTMLTokenizer::bufferCharacter
+        https://bugs.webkit.org/show_bug.cgi?id=140179
+
+        Reviewed by Anders Carlsson.
+
+        * fast/parser/numeric-entities-expected.txt: Added.
+        * fast/parser/numeric-entities.html: Added.
+
 2015-01-08  Eric Carlson  <eric.carlson@apple.com>
 
         After updating tests to use kerning, ligatures, and printer fonts, some tests fail
diff --git a/LayoutTests/fast/parser/numeric-entities-expected.txt b/LayoutTests/fast/parser/numeric-entities-expected.txt
new file mode 100644 (file)
index 0000000..ce91ead
--- /dev/null
@@ -0,0 +1,65 @@
+PASS testEntity('&#x0;') is "0xFFFD"
+PASS testEntity('&#x10000;') is "0xD800, 0xDC00"
+PASS testEntity('&#x10FFFF;') is "0xDBFF, 0xDFFF"
+PASS testEntity('&#x110000;') is "0xFFFD"
+PASS testEntity('&#xFFFFFF;') is "0xFFFD"
+PASS testEntity('&#xFFFFFFF;') is "0xFFFD"
+PASS testEntity('&#xFFFFFFFF;') is "0xFFFD"
+PASS testEntity('&#xFFFFFFFFF;') is "0xFFFD"
+PASS testEntity('&#xA0A103A0;') is "0xFFFD"
+PASS testEntity('&#x10000;') is "0xD800, 0xDC00"
+PASS testEntity('&#x1000010000;') is "0xFFFD"
+PASS testEntity('&#1;') is "0x1"
+PASS testEntity('&#11;') is "0xB"
+PASS testEntity('&#111;') is "0x6F"
+PASS testEntity('&#1111;') is "0x457"
+PASS testEntity('&#11111;') is "0x2B67"
+PASS testEntity('&#111111;') is "0xD82C, 0xDE07"
+PASS testEntity('&#1111111;') is "0xDBFD, 0xDC47"
+PASS testEntity('&#11111111;') is "0xFFFD"
+PASS testEntity('&#111111111;') is "0xFFFD"
+PASS testEntity('&#1111111111;') is "0xFFFD"
+PASS testEntity('&#11111111111;') is "0xFFFD"
+PASS testEntity('&#x1;') is "0x1"
+PASS testEntity('&#x9;') is "0x9"
+PASS testEntity('&#xA;') is "0xA"
+PASS testEntity('&#xB;') is "0xB"
+PASS testEntity('&#xC;') is "0xC"
+PASS testEntity('&#xD;') is "0xD"
+PASS testEntity('&#x7F;') is "0x7F"
+PASS testEntity('&#x81;') is "0x81"
+PASS testEntity('&#x8D;') is "0x8D"
+PASS testEntity('&#x8F;') is "0x8F"
+PASS testEntity('&#x90;') is "0x90"
+PASS testEntity('&#x9D;') is "0x9D"
+PASS testEntity('&#x80;') is "0x20AC"
+PASS testEntity('&#x82;') is "0x201A"
+PASS testEntity('&#x83;') is "0x192"
+PASS testEntity('&#x84;') is "0x201E"
+PASS testEntity('&#x85;') is "0x2026"
+PASS testEntity('&#x86;') is "0x2020"
+PASS testEntity('&#x87;') is "0x2021"
+PASS testEntity('&#x88;') is "0x2C6"
+PASS testEntity('&#x89;') is "0x2030"
+PASS testEntity('&#x8A;') is "0x160"
+PASS testEntity('&#x8B;') is "0x2039"
+PASS testEntity('&#x8C;') is "0x152"
+PASS testEntity('&#x8E;') is "0x17D"
+PASS testEntity('&#x91;') is "0x2018"
+PASS testEntity('&#x92;') is "0x2019"
+PASS testEntity('&#x93;') is "0x201C"
+PASS testEntity('&#x94;') is "0x201D"
+PASS testEntity('&#x95;') is "0x2022"
+PASS testEntity('&#x96;') is "0x2013"
+PASS testEntity('&#x97;') is "0x2014"
+PASS testEntity('&#x98;') is "0x2DC"
+PASS testEntity('&#x99;') is "0x2122"
+PASS testEntity('&#x9A;') is "0x161"
+PASS testEntity('&#x9B;') is "0x203A"
+PASS testEntity('&#x9C;') is "0x153"
+PASS testEntity('&#x9E;') is "0x17E"
+PASS testEntity('&#x9F;') is "0x178"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/parser/numeric-entities.html b/LayoutTests/fast/parser/numeric-entities.html
new file mode 100644 (file)
index 0000000..362bb69
--- /dev/null
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+
+if (window.testRunner)
+    testRunner.dumpAsText();
+
+function testEntity(entity)
+{
+    var element = document.createElement("div")
+    element.innerHTML = entity;
+    var string = element.firstChild.data;
+    var result = "";
+    for (var i = 0; i < string.length; ++i) {
+        if (result.length)
+            result += ", "
+        result += "0x" + string.charCodeAt(i).toString(16).toUpperCase();
+    }
+    return result;
+}
+
+shouldBeEqualToString("testEntity('&#x0;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#x10000;')", "0xD800, 0xDC00");
+shouldBeEqualToString("testEntity('&#x10FFFF;')", "0xDBFF, 0xDFFF");
+shouldBeEqualToString("testEntity('&#x110000;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#xFFFFFF;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#xFFFFFFF;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#xFFFFFFFF;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#xFFFFFFFFF;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#xA0A103A0;')", "0xFFFD");
+
+shouldBeEqualToString("testEntity('&#x10000;')", "0xD800, 0xDC00");
+shouldBeEqualToString("testEntity('&#x1000010000;')", "0xFFFD");
+
+shouldBeEqualToString("testEntity('&#1;')", "0x1");
+shouldBeEqualToString("testEntity('&#11;')", "0xB");
+shouldBeEqualToString("testEntity('&#111;')", "0x6F");
+shouldBeEqualToString("testEntity('&#1111;')", "0x457");
+shouldBeEqualToString("testEntity('&#11111;')", "0x2B67");
+shouldBeEqualToString("testEntity('&#111111;')", "0xD82C, 0xDE07");
+shouldBeEqualToString("testEntity('&#1111111;')", "0xDBFD, 0xDC47");
+shouldBeEqualToString("testEntity('&#11111111;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#111111111;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#1111111111;')", "0xFFFD");
+shouldBeEqualToString("testEntity('&#11111111111;')", "0xFFFD");
+
+shouldBeEqualToString("testEntity('&#x1;')", "0x1");
+shouldBeEqualToString("testEntity('&#x9;')", "0x9");
+shouldBeEqualToString("testEntity('&#xA;')", "0xA");
+shouldBeEqualToString("testEntity('&#xB;')", "0xB");
+shouldBeEqualToString("testEntity('&#xC;')", "0xC");
+shouldBeEqualToString("testEntity('&#xD;')", "0xD");
+shouldBeEqualToString("testEntity('&#x7F;')", "0x7F");
+shouldBeEqualToString("testEntity('&#x81;')", "0x81");
+shouldBeEqualToString("testEntity('&#x8D;')", "0x8D");
+shouldBeEqualToString("testEntity('&#x8F;')", "0x8F");
+shouldBeEqualToString("testEntity('&#x90;')", "0x90");
+shouldBeEqualToString("testEntity('&#x9D;')", "0x9D");
+
+shouldBeEqualToString("testEntity('&#x80;')", "0x20AC");
+shouldBeEqualToString("testEntity('&#x82;')", "0x201A");
+shouldBeEqualToString("testEntity('&#x83;')", "0x192");
+shouldBeEqualToString("testEntity('&#x84;')", "0x201E");
+shouldBeEqualToString("testEntity('&#x85;')", "0x2026");
+shouldBeEqualToString("testEntity('&#x86;')", "0x2020");
+shouldBeEqualToString("testEntity('&#x87;')", "0x2021");
+shouldBeEqualToString("testEntity('&#x88;')", "0x2C6");
+shouldBeEqualToString("testEntity('&#x89;')", "0x2030");
+shouldBeEqualToString("testEntity('&#x8A;')", "0x160");
+shouldBeEqualToString("testEntity('&#x8B;')", "0x2039");
+shouldBeEqualToString("testEntity('&#x8C;')", "0x152");
+shouldBeEqualToString("testEntity('&#x8E;')", "0x17D");
+shouldBeEqualToString("testEntity('&#x91;')", "0x2018");
+shouldBeEqualToString("testEntity('&#x92;')", "0x2019");
+shouldBeEqualToString("testEntity('&#x93;')", "0x201C");
+shouldBeEqualToString("testEntity('&#x94;')", "0x201D");
+shouldBeEqualToString("testEntity('&#x95;')", "0x2022");
+shouldBeEqualToString("testEntity('&#x96;')", "0x2013");
+shouldBeEqualToString("testEntity('&#x97;')", "0x2014");
+shouldBeEqualToString("testEntity('&#x98;')", "0x2DC");
+shouldBeEqualToString("testEntity('&#x99;')", "0x2122");
+shouldBeEqualToString("testEntity('&#x9A;')", "0x161");
+shouldBeEqualToString("testEntity('&#x9B;')", "0x203A");
+shouldBeEqualToString("testEntity('&#x9C;')", "0x153");
+shouldBeEqualToString("testEntity('&#x9E;')", "0x17E");
+shouldBeEqualToString("testEntity('&#x9F;')", "0x178");
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</head>
+<body>
+</body>
+</html>
index b3a6924..e854004 100644 (file)
@@ -1,3 +1,24 @@
+2015-01-08  Darin Adler  <darin@apple.com>
+
+        ASSERTION FAILED: character != kEndOfFileMarker in WebCore::HTMLTokenizer::bufferCharacter
+        https://bugs.webkit.org/show_bug.cgi?id=140179
+
+        Reviewed by Anders Carlsson.
+
+        Test: fast/parser/numeric-entities.html
+
+        * html/parser/HTMLEntityParser.cpp:
+        (WebCore::HTMLEntityParser::legalEntityFor): Merged adjustEntity logic in here.
+        Since the type UChar32 is a signed integer, need to check for <= 0, not just 0.
+        This <= change alone would have fixed the bug.
+
+        * xml/parser/CharacterReferenceParserInlines.h:
+        (WebCore::consumeCharacterReference): Added overflow checking when parsing hex
+        and decimal character references. This change alone would also have fixed the
+        bug, but in addition it makes overflow cases reliably generate replacement
+        characters rather than ignoring the overflow and producing seemingly random
+        characters. Test cases cover the original reported bug and other overflow cases.
+
 2015-01-08  Dean Jackson  <dino@apple.com>
 
         Text not drawn or white-on-white for "Close Page"/"Go Back" button on safe browsing warning page
index 8d2177f..a016050 100644 (file)
@@ -51,23 +51,15 @@ static inline bool isAlphaNumeric(UChar cc)
 
 class HTMLEntityParser {
 public:
-    inline static UChar adjustEntity(UChar32 value)
+    static UChar32 legalEntityFor(UChar32 value)
     {
-        if ((value & ~0x1F) != 0x0080)
+        if (value <= 0 || value > 0x10FFFF || (value >= 0xD800 && value <= 0xDFFF))
+            return 0xFFFD;
+        if ((value & ~0x1F) != 0x80)
             return value;
         return windowsLatin1ExtensionArray[value - 0x80];
     }
 
-    inline static UChar32 legalEntityFor(UChar32 value)
-    {
-        // FIXME: A number of specific entity values generate parse errors.
-        if (!value || value > 0x10FFFF || (value >= 0xD800 && value <= 0xDFFF))
-            return 0xFFFD;
-        if (U_IS_BMP(value))
-            return adjustEntity(value);
-        return value;
-    }
-
     inline static bool acceptMalformed() { return true; }
 
     inline static bool consumeNamedEntity(SegmentedString& source, StringBuilder& decodedEntity, bool& notEnoughCharacters, UChar additionalAllowedCharacter, UChar& cc)
index 62780c7..681eb33 100644 (file)
@@ -65,6 +65,8 @@ bool consumeCharacterReference(SegmentedString& source, StringBuilder& decodedCh
     };
     EntityState entityState = Initial;
     UChar32 result = 0;
+    bool overflow = false;
+    const UChar32 highestValidCharacter = 0x10FFFF;
     StringBuilder consumedCharacters;
     
     while (!source.isEmpty()) {
@@ -128,15 +130,17 @@ bool consumeCharacterReference(SegmentedString& source, StringBuilder& decodedCh
                 result = result * 16 + 10 + cc - 'A';
             else if (cc == ';') {
                 source.advanceAndASSERT(cc);
-                decodedCharacter.append(ParserFunctions::legalEntityFor(result));
+                decodedCharacter.append(ParserFunctions::legalEntityFor(overflow ? 0 : result));
                 return true;
             } else if (ParserFunctions::acceptMalformed()) {
-                decodedCharacter.append(ParserFunctions::legalEntityFor(result));
+                decodedCharacter.append(ParserFunctions::legalEntityFor(overflow ? 0 : result));
                 return true;
             } else {
                 unconsumeCharacters(source, consumedCharacters);
                 return false;
             }
+            if (result > highestValidCharacter)
+                overflow = true;
             break;
         }
         case Decimal: {
@@ -144,15 +148,17 @@ bool consumeCharacterReference(SegmentedString& source, StringBuilder& decodedCh
                 result = result * 10 + cc - '0';
             else if (cc == ';') {
                 source.advanceAndASSERT(cc);
-                decodedCharacter.append(ParserFunctions::legalEntityFor(result));
+                decodedCharacter.append(ParserFunctions::legalEntityFor(overflow ? 0 : result));
                 return true;
             } else if (ParserFunctions::acceptMalformed()) {
-                decodedCharacter.append(ParserFunctions::legalEntityFor(result));
+                decodedCharacter.append(ParserFunctions::legalEntityFor(overflow ? 0 : result));
                 return true;
             } else {
                 unconsumeCharacters(source, consumedCharacters);
                 return false;
             }
+            if (result > highestValidCharacter)
+                overflow = true;
             break;
         }
         case Named: {