Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<!--` repeated many times. A parser is created for
// each inline snippet, so these don't need to be reset.
private boolean noProcessingInstructionEnd;
private boolean noCommentEnd;
private boolean noCdataEnd;
private boolean noDeclarationEnd;

@Override
public ParsedInline tryParse(InlineParserState inlineParserState) {
Scanner scanner = inlineParserState.scanner();
Expand Down Expand Up @@ -146,24 +156,38 @@ private static boolean tryClosingTag(Scanner scanner) {
return false;
}

private static boolean tryProcessingInstruction(Scanner scanner) {
private boolean tryProcessingInstruction(Scanner scanner) {
// spec: A processing instruction consists of the string <?, a string of characters not
// including the string ?>, and the string ?>.
if (noProcessingInstructionEnd) {
return false;
}
scanner.next();
while (scanner.find('?') > 0) {
int found;
while ((found = scanner.find('?')) > 0) {
scanner.next();
if (scanner.next('>')) {
return true;
}
}
// The loop also ends when a `?` is found directly at the current position (`found == 0`),
// which is not the end of the input, so only remember the miss when the scan really did
// reach the end.
noProcessingInstructionEnd = found < 0;
return false;
}

private static boolean tryComment(Scanner scanner) {
private boolean tryComment(Scanner scanner) {
// spec: An [HTML comment](@) consists of `<!-->`, `<!--->`, or `<!--`, a string of
// characters not including the string `-->`, 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('-')) {
Expand All @@ -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 <![CDATA[, a string of characters not
// including the string ]]>, and the string ]]>.

if (noCdataEnd) {
return false;
}

// Skip `[`
scanner.next();

Expand All @@ -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 <!, an ASCII letter, zero or more characters
// not including the character >, and the character >.
if (noDeclarationEnd) {
return false;
}
scanner.match(asciiLetter);
if (scanner.whitespace() <= 0) {
return false;
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ public void cdata() {
assertRendering("inline <![CDATA[ ] ]] ]]>", "<p>inline <![CDATA[ ] ]] ]]></p>\n");
}

@Test
public void afterFailedAttempt() {
// A `<` that doesn't start inline HTML must not stop a later one from being parsed
assertRendering("inline <!- <!-- a -->", "<p>inline &lt;!- <!-- a --></p>\n");
assertRendering("inline <?? <?php ?>", "<p>inline &lt;?? <?php ?></p>\n");
assertRendering(
"inline <![x]]> <![CDATA[a]]>", "<p>inline &lt;![x]]&gt; <![CDATA[a]]></p>\n");
assertRendering("inline <!foo> <!bar baz>", "<p>inline &lt;!foo&gt; <!bar baz></p>\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 <?a\n\nx <?b?>", "<p>x &lt;?a</p>\n<p>x <?b?></p>\n");
assertRendering("x <!--a\n\nx <!--b-->", "<p>x &lt;!--a</p>\n<p>x <!--b--></p>\n");
assertRendering(
"x <![CDATA[a\n\nx <![CDATA[b]]>",
"<p>x &lt;![CDATA[a</p>\n<p>x <![CDATA[b]]></p>\n");
assertRendering("x <!A a\n\nx <!A b>", "<p>x &lt;!A a</p>\n<p>x <!A b></p>\n");
}

@Test
public void declaration() {
// Whitespace is mandatory
Expand Down
25 changes: 25 additions & 0 deletions commonmark/src/test/java/org/commonmark/test/PathologicalTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,29 @@ public void unclosedInlineLinks() {
// See https://github.com/commonmark/commonmark.js/issues/129
assertRendering("[](".repeat(x) + "\n", "<p>" + "[](".repeat(x) + "</p>\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), "<p>" + "x &lt;? &gt;".repeat(x) + "</p>\n");
}

@Test
public void htmlCommentsWithNoEnd() {
// `-->` never occurs because of the space
assertRendering("x <!-- >".repeat(x), "<p>" + "x &lt;!-- &gt;".repeat(x) + "</p>\n");
}

@Test
public void htmlCdataWithNoEnd() {
// `]]>` never occurs, there's no `]` at all
assertRendering(
"x <![CDATA[ >".repeat(x), "<p>" + "x &lt;![CDATA[ &gt;".repeat(x) + "</p>\n");
}
}
Loading