From 946b6c5e63a82d324f00b0f78cd39a401bbd127c Mon Sep 17 00:00:00 2001 From: fromveeko <77726018+fromVeeko@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:31:25 +0200 Subject: [PATCH] fix #447 --- .../internal/inline/HtmlInlineParser.java | 47 +++++++++++++++++-- .../commonmark/test/HtmlInlineParserTest.java | 22 +++++++++ .../org/commonmark/test/PathologicalTest.java | 25 ++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) 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

\n

x

\n"); + assertRendering("x ", "

x <!--a

\n

x

\n"); + assertRendering( + "x ", + "

x <![CDATA[a

\n

x

\n"); + assertRendering("x ", "

x <!A a

\n

x

\n"); + } + @Test public void declaration() { // Whitespace is mandatory diff --git a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java index 663aa7ced..8ef6de344 100644 --- a/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java +++ b/commonmark/src/test/java/org/commonmark/test/PathologicalTest.java @@ -88,4 +88,29 @@ public void unclosedInlineLinks() { // See https://github.com/commonmark/commonmark.js/issues/129 assertRendering("[](".repeat(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