diff --git a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java index 2e92c9be..2e25289e 100644 --- a/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java +++ b/commonmark/src/main/java/org/commonmark/internal/util/Html5Entities.java @@ -30,7 +30,11 @@ public static String entityToString(String input) { try { int codePoint = Integer.parseInt(value, base); - if (codePoint == 0) { + // The spec says that invalid Unicode code points are replaced by the REPLACEMENT + // CHARACTER. The surrogate range is not a set of valid code points. + if (codePoint == 0 + || (codePoint >= Character.MIN_SURROGATE + && codePoint <= Character.MAX_SURROGATE)) { return "\uFFFD"; } return new String(Character.toChars(codePoint)); diff --git a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java index 59382b28..8de5e209 100644 --- a/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java +++ b/commonmark/src/test/java/org/commonmark/test/SpecialInputTest.java @@ -19,6 +19,19 @@ public void nullCharacterEntityShouldBeReplaced() { assertRendering("foobar", "
foo\uFFFDbar
\n"); } + @Test + public void surrogateCharacterReferenceShouldBeReplaced() { + // The reference is well-formed, but a surrogate is not a valid Unicode code point, so it + // must be replaced by U+FFFD (same as cmark and commonmark.js) + assertRendering("", "\uFFFD
\n"); + assertRendering("", "\uFFFD
\n"); + assertRendering("", "\uFFFD
\n"); + assertRendering("beforeafter", "before\uFFFDafter
\n"); + // Code points outside the surrogate range are not affected + assertRendering("😀", "\uD83D\uDE00
\n"); + assertRendering("", "\uDBFF\uDFFF
\n"); + } + @Test public void crLfAsLineSeparatorShouldBeParsed() { assertRendering("foo\r\nbar", "foo\nbar
\n");