diff --git a/changelog_unreleased/markdown/19849.md b/changelog_unreleased/markdown/19849.md new file mode 100644 index 000000000..9121e3c58 --- /dev/null +++ b/changelog_unreleased/markdown/19849.md @@ -0,0 +1,16 @@ +#### Fix escaped character in links (#19849 by @Kjubikstronk) + + +```markdown + +[test](/hello?foo"\;bar) + + +[test](/hello?foo"bar) + + +[test](/hello?foo"bar) + + +[test](/hello?foo\"bar) +``` diff --git a/src/language-markdown/print/mdast.js b/src/language-markdown/print/mdast.js index e754480c0..303cfa957 100644 --- a/src/language-markdown/print/mdast.js +++ b/src/language-markdown/print/mdast.js @@ -478,6 +478,11 @@ function shouldRemainTheSameContent(path) { ); } +// https://spec.commonmark.org/0.31.2/#entity-and-numeric-character-references +const characterReferenceRegex = /&(?=(?:#x[\da-f]+|#\d+|[\da-z]+);)/gi; +const escapeCharacterReferences = (value) => + value.replaceAll(characterReferenceRegex, String.raw`\&`); + /** * @param {string} url * @param {boolean} unwrapBalancedParens @@ -487,6 +492,7 @@ function printUrl(url, unwrapBalancedParens) { // Backslash followed by ASCII punctuation would be misinterpreted as an // escape sequence, so must itself be escaped. url = url.replaceAll(/\\(?![^!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g, "\\\\"); + url = escapeCharacterReferences(url); // CommonMark forbids ASCII controls, space, unbalanced parentheses, and // initial <, unless wrapped in <> with any inner < or > escaped. CommonMark @@ -526,11 +532,13 @@ function printTitle(title, options, printSpace = true) { !title.includes(")") ) { title = title.replaceAll("\\", "\\\\"); + title = escapeCharacterReferences(title); return `(${title})`; // avoid escaped quotes } const quote = getPreferredQuote(title, options.singleQuote); title = title.replaceAll("\\", "\\\\"); title = title.replaceAll(quote, `\\${quote}`); + title = escapeCharacterReferences(title); return `${quote}${title}${quote}`; } diff --git a/tests/format/markdown/link/__snapshots__/format.test.js.snap b/tests/format/markdown/link/__snapshots__/format.test.js.snap index b85d32994..1e6145bef 100644 --- a/tests/format/markdown/link/__snapshots__/format.test.js.snap +++ b/tests/format/markdown/link/__snapshots__/format.test.js.snap @@ -141,9 +141,65 @@ proseWrap: "always" =====================================input====================================== [Test](http://localhost:8080/test?language=DE¤cy=EUR) +[Test](/a?x"\\;b) + +[Test](/a?x"\\;b) + +[Test](/a?x"\\;b) + +![Test](/a?x"\\;b) + +[Test](/a "x"\\;b") + +[Test]() + +[Test]: /a?x"\\;b + +[Test](/a?x\\&b) + +[Test](/a?x\\&quot;b) + +[Test](/a "x\\&b") + +[Test](/a "x\\&quot;b") + +[Test](/a?x&b) + +[Test](/a?x&quot;b) + +[Test](/a "/a?x&quot;b") + =====================================output===================================== [Test](http://localhost:8080/test?language=DE¤cy=EUR) +[Test](/a?x\\"b) + +[Test](/a?x\\"b) + +[Test](/a?x\\"b) + +![Test](/a?x\\"b) + +[Test](/a "x\\"b") + +[Test]() + +[Test]: /a?x\\"b + +[Test](/a?x\\&b) + +[Test](/a?x\\&quot;b) + +[Test](/a "x\\&b") + +[Test](/a "x\\&quot;b") + +[Test](/a?x&b) + +[Test](/a?x\\"b) + +[Test](/a "/a?x\\"b") + ================================================================================ `; diff --git a/tests/format/markdown/link/entity.md b/tests/format/markdown/link/entity.md index 009cede7e..9b9001d91 100644 --- a/tests/format/markdown/link/entity.md +++ b/tests/format/markdown/link/entity.md @@ -1 +1,29 @@ [Test](http://localhost:8080/test?language=DE¤cy=EUR) + +[Test](/a?x"\;b) + +[Test](/a?x"\;b) + +[Test](/a?x"\;b) + +![Test](/a?x"\;b) + +[Test](/a "x"\;b") + +[Test]() + +[Test]: /a?x"\;b + +[Test](/a?x\&b) + +[Test](/a?x\&quot;b) + +[Test](/a "x\&b") + +[Test](/a "x\&quot;b") + +[Test](/a?x&b) + +[Test](/a?x&quot;b) + +[Test](/a "/a?x&quot;b")