Avoid corrupting empty URL + title, or URL with <, (, \ (#19150)
Signed-off-by: Anders Kaseorg <andersk@mit.edu>
This commit is contained in:
1 parent
37255cd58b
commit
98074bc210
6 files changed
+151
-46
No files matched your search
@@ -0,0 +1,15 @@
|
||||
#### Fix URL formatting edge cases (#19150 by @andersk)
|
||||
|
||||
Avoid corrupting a link, image, or link reference definition with an empty URL and a title, or with certain characters like `<`, `(`, `\`, by using `<...>` wrapping and `\` escaping as appropriate.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```md
|
||||
<!-- Input -->
|
||||
[link](<> "title"), 
|
||||
|
||||
<!-- Prettier stable -->
|
||||
[link]( "title"), 
|
||||
|
||||
<!-- Prettier main -->
|
||||
[link](<> "title"), 
|
||||
```
|
||||
@@ -1,5 +1,4 @@
|
||||
import collapseWhiteSpace from "collapse-white-space";
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import {
|
||||
align,
|
||||
DOC_TYPE_STRING,
|
||||
@@ -206,7 +205,7 @@ function printMdast(path, options, print) {
|
||||
"[",
|
||||
printChildren(path, options, print),
|
||||
"](",
|
||||
printUrl(node.url, ")"),
|
||||
node.url === "" && node.title !== null ? "<>" : printUrl(node.url),
|
||||
printTitle(node.title, options),
|
||||
")",
|
||||
];
|
||||
@@ -221,7 +220,7 @@ function printMdast(path, options, print) {
|
||||
""),
|
||||
node.url === "" && node.title !== null ? "<>" : printUrl(node.url),
|
||||
printTitle(node.title, options),
|
||||
")",
|
||||
];
|
||||
@@ -489,30 +488,23 @@ function shouldRemainTheSameContent(path) {
|
||||
);
|
||||
}
|
||||
|
||||
const encodeUrl = (url, characters) => {
|
||||
for (const character of characters) {
|
||||
url = url.replaceAll(character, encodeURIComponent(character));
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string[] | string} [dangerousCharOrChars]
|
||||
* @returns {string}
|
||||
*/
|
||||
function printUrl(url, dangerousCharOrChars = []) {
|
||||
const dangerousChars = [
|
||||
" ",
|
||||
...(Array.isArray(dangerousCharOrChars)
|
||||
? dangerousCharOrChars
|
||||
: [dangerousCharOrChars]),
|
||||
];
|
||||
|
||||
return new RegExp(
|
||||
dangerousChars.map((x) => escapeStringRegexp(x)).join("|"),
|
||||
).test(url)
|
||||
? `<${encodeUrl(url, "<>")}>`
|
||||
function printUrl(url) {
|
||||
// Backslash followed by ASCII punctuation would be misinterpreted as an
|
||||
// escape sequence, so must itself be escaped.
|
||||
url = url.replaceAll(/\\(?![^!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g, "\\\\");
|
||||
// CommonMark forbids ASCII controls, space, unbalanced parentheses, and
|
||||
// initial <, unless wrapped in <> with any inner < or > escaped. CommonMark
|
||||
// only suggests implementations "should" support at least three levels of
|
||||
// parenthesis nesting, so it's unclear whether we can safely rely on three
|
||||
// levels as we do here, but we certainly can't expect more.
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return /[\x00-\x1f\x7f ]|^</.test(url) ||
|
||||
!/^(?:[^()]|\((?:[^()]|\((?:[^()]|\([^()]*\))*\))*\))*$/.test(url)
|
||||
? `<${url.replaceAll(/([<>])/g, String.raw`\$1`)}>`
|
||||
: url;
|
||||
}
|
||||
|
||||
@@ -529,7 +521,13 @@ function printTitle(title, options, printSpace = true) {
|
||||
title = title.replaceAll(/\\(?=["')])/g, "");
|
||||
}
|
||||
|
||||
if (title.includes('"') && title.includes("'") && !title.includes(")")) {
|
||||
if (
|
||||
title.includes('"') &&
|
||||
title.includes("'") &&
|
||||
!title.includes("(") &&
|
||||
!title.includes(")")
|
||||
) {
|
||||
title = title.replaceAll("\\", "\\\\");
|
||||
return `(${title})`; // avoid escaped quotes
|
||||
}
|
||||
const quote = getPreferredQuote(title, options.singleQuote);
|
||||
|
||||
+3
-3
@@ -981,7 +981,7 @@ proseWrap: "always"
|
||||
[link]((foo)and(bar))
|
||||
|
||||
=====================================output=====================================
|
||||
[link](<(foo)and(bar)>)
|
||||
[link]((foo)and(bar))
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
@@ -995,7 +995,7 @@ proseWrap: "always"
|
||||
[link](foo(and\\(bar\\)))
|
||||
|
||||
=====================================output=====================================
|
||||
[link](<foo(and(bar))>)
|
||||
[link](foo(and(bar)))
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
@@ -1009,7 +1009,7 @@ proseWrap: "always"
|
||||
[link](<foo(and(bar))>)
|
||||
|
||||
=====================================output=====================================
|
||||
[link](<foo(and(bar))>)
|
||||
[link](foo(and(bar)))
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
@@ -8341,7 +8341,7 @@ proseWrap: "always"
|
||||
[link](\\(foo\\))
|
||||
|
||||
=====================================output=====================================
|
||||
[link](<(foo)>)
|
||||
[link]((foo))
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
@@ -8355,7 +8355,7 @@ proseWrap: "always"
|
||||
[link](foo(and(bar)))
|
||||
|
||||
=====================================output=====================================
|
||||
[link](<foo(and(bar))>)
|
||||
[link](foo(and(bar)))
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
@@ -57,28 +57,63 @@ singleQuote: true
|
||||
[link](https://www.google.fr/foo-%3Ebar)
|
||||
[link](https://www.google.fr/foo-<bar)
|
||||
[link](https://www.google.fr/foo-%3Cbar)
|
||||
[link](\\<)
|
||||
[link](\\()
|
||||
[link](\\\\)
|
||||
[link](< >)
|
||||
[link](<> "title")
|
||||
[link](<>)
|
||||
[link]()
|
||||
|
||||
foo->bar)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
![link]()
|
||||
|
||||
[link]: https://www.google.fr/()foo->bar
|
||||
[link]: https://www.google.fr/foo->bar
|
||||
[link]: https://www.google.fr/foo-%3Ebar
|
||||
[link]: https://www.google.fr/foo-<bar
|
||||
[link]: https://www.google.fr/foo-%3Cbar
|
||||
[link]: \\<
|
||||
[link]: \\(
|
||||
[link]: \\\\
|
||||
[link]: < >
|
||||
[link]: <> "title"
|
||||
[link]: <>
|
||||
|
||||
=====================================output=====================================
|
||||
[link](<https://www.google.fr/()foo-%3Ebar>)
|
||||
[link](https://www.google.fr/foo->bar) [link](https://www.google.fr/foo-%3Ebar)
|
||||
[link](https://www.google.fr/foo-<bar) [link](https://www.google.fr/foo-%3Cbar)
|
||||
foo-%3Ebar>)
|
||||
[link](https://www.google.fr/()foo->bar) [link](https://www.google.fr/foo->bar)
|
||||
[link](https://www.google.fr/foo-%3Ebar) [link](https://www.google.fr/foo-<bar)
|
||||
[link](https://www.google.fr/foo-%3Cbar) [link](<\\<>) [link](<(>) [link](\\\\)
|
||||
[link](< >) [link](<> 'title') [link]() [link]()
|
||||
|
||||
foo->bar)
|
||||

|
||||

|
||||

|
||||
 [link]:
|
||||
https://www.google.fr/()foo->bar [link]: https://www.google.fr/foo->bar [link]:
|
||||
https://www.google.fr/foo-%3Ebar [link]: https://www.google.fr/foo-<bar [link]:
|
||||
https://www.google.fr/foo-%3Cbar
|
||||
   
|
||||
  ![link]() ![link]()
|
||||
|
||||
[link]: https://www.google.fr/()foo->bar
|
||||
[link]: https://www.google.fr/foo->bar
|
||||
[link]: https://www.google.fr/foo-%3Ebar
|
||||
[link]: https://www.google.fr/foo-<bar
|
||||
[link]: https://www.google.fr/foo-%3Cbar
|
||||
[link]: <\\<>
|
||||
[link]: <(>
|
||||
[link]: \\\\
|
||||
[link]: < >
|
||||
[link]: <> 'title'
|
||||
[link]: <>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
@@ -94,28 +129,63 @@ proseWrap: "always"
|
||||
[link](https://www.google.fr/foo-%3Ebar)
|
||||
[link](https://www.google.fr/foo-<bar)
|
||||
[link](https://www.google.fr/foo-%3Cbar)
|
||||
[link](\\<)
|
||||
[link](\\()
|
||||
[link](\\\\)
|
||||
[link](< >)
|
||||
[link](<> "title")
|
||||
[link](<>)
|
||||
[link]()
|
||||
|
||||
foo->bar)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
![link]()
|
||||
|
||||
[link]: https://www.google.fr/()foo->bar
|
||||
[link]: https://www.google.fr/foo->bar
|
||||
[link]: https://www.google.fr/foo-%3Ebar
|
||||
[link]: https://www.google.fr/foo-<bar
|
||||
[link]: https://www.google.fr/foo-%3Cbar
|
||||
[link]: \\<
|
||||
[link]: \\(
|
||||
[link]: \\\\
|
||||
[link]: < >
|
||||
[link]: <> "title"
|
||||
[link]: <>
|
||||
|
||||
=====================================output=====================================
|
||||
[link](<https://www.google.fr/()foo-%3Ebar>)
|
||||
[link](https://www.google.fr/foo->bar) [link](https://www.google.fr/foo-%3Ebar)
|
||||
[link](https://www.google.fr/foo-<bar) [link](https://www.google.fr/foo-%3Cbar)
|
||||
foo-%3Ebar>)
|
||||
[link](https://www.google.fr/()foo->bar) [link](https://www.google.fr/foo->bar)
|
||||
[link](https://www.google.fr/foo-%3Ebar) [link](https://www.google.fr/foo-<bar)
|
||||
[link](https://www.google.fr/foo-%3Cbar) [link](<\\<>) [link](<(>) [link](\\\\)
|
||||
[link](< >) [link](<> "title") [link]() [link]()
|
||||
|
||||
foo->bar)
|
||||

|
||||

|
||||

|
||||
 [link]:
|
||||
https://www.google.fr/()foo->bar [link]: https://www.google.fr/foo->bar [link]:
|
||||
https://www.google.fr/foo-%3Ebar [link]: https://www.google.fr/foo-<bar [link]:
|
||||
https://www.google.fr/foo-%3Cbar
|
||||
   
|
||||
  ![link]() ![link]()
|
||||
|
||||
[link]: https://www.google.fr/()foo->bar
|
||||
[link]: https://www.google.fr/foo->bar
|
||||
[link]: https://www.google.fr/foo-%3Ebar
|
||||
[link]: https://www.google.fr/foo-<bar
|
||||
[link]: https://www.google.fr/foo-%3Cbar
|
||||
[link]: <\\<>
|
||||
[link]: <(>
|
||||
[link]: \\\\
|
||||
[link]: < >
|
||||
[link]: <> "title"
|
||||
[link]: <>
|
||||
|
||||
================================================================================
|
||||
`;
|
||||
|
||||
@@ -3,13 +3,35 @@
|
||||
[link](https://www.google.fr/foo-%3Ebar)
|
||||
[link](https://www.google.fr/foo-<bar)
|
||||
[link](https://www.google.fr/foo-%3Cbar)
|
||||
[link](\<)
|
||||
[link](\()
|
||||
[link](\\)
|
||||
[link](< >)
|
||||
[link](<> "title")
|
||||
[link](<>)
|
||||
[link]()
|
||||
|
||||
foo->bar)
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
![link]()
|
||||
|
||||
[link]: https://www.google.fr/()foo->bar
|
||||
[link]: https://www.google.fr/foo->bar
|
||||
[link]: https://www.google.fr/foo-%3Ebar
|
||||
[link]: https://www.google.fr/foo-<bar
|
||||
[link]: https://www.google.fr/foo-%3Cbar
|
||||
[link]: https://www.google.fr/foo-%3Cbar
|
||||
[link]: \<
|
||||
[link]: \(
|
||||
[link]: \\
|
||||
[link]: < >
|
||||
[link]: <> "title"
|
||||
[link]: <>
|
||||
Reference in new issue
Block a user