diff --git a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java index b359f732b..a34346da0 100644 --- a/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java +++ b/commonmark/src/main/java/org/commonmark/internal/inline/HtmlInlineParser.java @@ -42,6 +42,16 @@ public class HtmlInlineParser implements InlineContentParser { .c('`') .build(); + // A scan that ran to the end of the input without finding the terminator it was looking for + // proves that no later scan in the same inline snippet can find it either (inline parsing only + // moves forward). Remembering that stops each `<` from scanning the rest of the input again, + // which would be quadratic for input like ``, ``, or ``, and `-->` (see the [HTML // spec](https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state)). + if (noCommentEnd) { + // Both `` and `` contain `-->` themselves, so this can't skip a valid short + // comment either. + return false; + } + // Skip first `-` scanner.next(); if (!scanner.next('-')) { @@ -182,13 +206,19 @@ private static boolean tryComment(Scanner scanner) { } } + // The loop above only ends when the scan reached the end of the input. + noCommentEnd = true; return false; } - private static boolean tryCdata(Scanner scanner) { + private boolean tryCdata(Scanner scanner) { // spec: A CDATA section consists of the string , and the string ]]>. + if (noCdataEnd) { + return false; + } + // Skip `[` scanner.next(); @@ -200,14 +230,19 @@ private static boolean tryCdata(Scanner scanner) { scanner.next(); } } + // The loop above only ends when the scan reached the end of the input. + noCdataEnd = true; } return false; } - private static boolean tryDeclaration(Scanner scanner) { + private boolean tryDeclaration(Scanner scanner) { // spec: A declaration consists of the string , and the character >. + if (noDeclarationEnd) { + return false; + } scanner.match(asciiLetter); if (scanner.whitespace() <= 0) { return false; @@ -216,6 +251,8 @@ private static boolean tryDeclaration(Scanner scanner) { scanner.next(); return true; } + // `find` only returns a negative value when it reached the end of the input. + noDeclarationEnd = true; return false; } diff --git a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java index 8e1fd9790..454c1de8b 100644 --- a/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java +++ b/commonmark/src/test/java/org/commonmark/test/HtmlInlineParserTest.java @@ -21,6 +21,28 @@ public void cdata() { assertRendering("inline ", "
inline
\n"); } + @Test + public void afterFailedAttempt() { + // A `<` that doesn't start inline HTML must not stop a later one from being parsed + assertRendering("inline ", "inline <!-
\n"); + assertRendering("inline ? ", "inline <??
\n"); + assertRendering( + "inline ", "inline <![x]]>
\n"); + assertRendering("inline ", "inline <!foo>
\n"); + } + + @Test + public void unterminatedConstructDoesNotAffectLaterParagraph() { + // A scan that reaches the end of the input only says something about the inline snippet it + // ran in, and a parser is created for each of those + assertRendering("x ", "x <?a
\nx
\n"); + assertRendering("x ", "x <!--a
\nx
\n"); + assertRendering( + "x ", + "x < + "\n", "" + "[](".repeat(x) + "
\n"); } + + // The following cases each start an inline HTML construct whose terminator never occurs, so + // every occurrence used to scan the rest of the input again. They all contain a `>` close to + // each `<` so that the separate autolink scan for `>` stays cheap and only the inline HTML + // scanning is measured. The leading text keeps the line from starting with `<`, which would + // parse as an HTML block instead. + + @Test + public void htmlProcessingInstructionsWithNoEnd() { + // `?>` never occurs because of the space + assertRendering("x >".repeat(x), "" + "x <? >".repeat(x) + "
\n"); + } + + @Test + public void htmlCommentsWithNoEnd() { + // `-->` never occurs because of the space + assertRendering("x