From 656c3e4aba6a00eb3664ff7e829878ed9222c339 Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Thu, 10 Dec 2020 10:27:58 -0600 Subject: [PATCH] fix: fix link with angle brackets around href (#1851) --- src/Tokenizer.js | 49 +++++++++++++++------- src/rules.js | 2 +- test/specs/commonmark/commonmark.0.29.json | 12 ++---- test/specs/gfm/commonmark.0.29.json | 12 ++---- test/specs/new/link_lt.html | 4 +- test/specs/new/link_lt.md | 5 +++ 6 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/Tokenizer.js b/src/Tokenizer.js index 20f122332f..972c5b9ea3 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -457,34 +457,56 @@ module.exports = class Tokenizer { link(src) { const cap = this.rules.inline.link.exec(src); if (cap) { - const lastParenIndex = findClosingBracket(cap[2], '()'); - if (lastParenIndex > -1) { - const start = cap[0].indexOf('!') === 0 ? 5 : 4; - const linkLen = start + cap[1].length + lastParenIndex; - cap[2] = cap[2].substring(0, lastParenIndex); - cap[0] = cap[0].substring(0, linkLen).trim(); - cap[3] = ''; + const trimmedUrl = cap[2].trim(); + if (!this.options.pedantic && trimmedUrl.startsWith('<')) { + // commonmark requires matching angle brackets + if (!trimmedUrl.endsWith('>')) { + return; + } + + // ending angle bracket cannot be escaped + const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), '\\'); + if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) { + return; + } + } else { + // find closing parenthesis + const lastParenIndex = findClosingBracket(cap[2], '()'); + if (lastParenIndex > -1) { + const start = cap[0].indexOf('!') === 0 ? 5 : 4; + const linkLen = start + cap[1].length + lastParenIndex; + cap[2] = cap[2].substring(0, lastParenIndex); + cap[0] = cap[0].substring(0, linkLen).trim(); + cap[3] = ''; + } } let href = cap[2]; let title = ''; if (this.options.pedantic) { + // split pedantic href and title const link = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href); if (link) { href = link[1]; title = link[3]; - } else { - title = ''; } } else { title = cap[3] ? cap[3].slice(1, -1) : ''; } - href = href.trim().replace(/^<([\s\S]*)>$/, '$1'); - const token = outputLink(cap, { + + href = href.trim(); + if (href.startsWith('<')) { + if (this.options.pedantic && !trimmedUrl.endsWith('>')) { + // pedantic allows starting angle bracket without ending angle bracket + href = href.slice(1); + } else { + href = href.slice(1, -1); + } + } + return outputLink(cap, { href: href ? href.replace(this.rules.inline._escapes, '$1') : href, title: title ? title.replace(this.rules.inline._escapes, '$1') : title }, cap[0]); - return token; } } @@ -502,8 +524,7 @@ module.exports = class Tokenizer { text }; } - const token = outputLink(cap, link, cap[0]); - return token; + return outputLink(cap, link, cap[0]); } } diff --git a/src/rules.js b/src/rules.js index 13356df367..df14188275 100644 --- a/src/rules.js +++ b/src/rules.js @@ -260,7 +260,7 @@ inline.tag = edit(inline.tag) .getRegex(); inline._label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/; -inline._href = /<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*/; +inline._href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/; inline._title = /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/; inline.link = edit(inline.link) diff --git a/test/specs/commonmark/commonmark.0.29.json b/test/specs/commonmark/commonmark.0.29.json index 1440e2f2c1..7d9e16f58a 100644 --- a/test/specs/commonmark/commonmark.0.29.json +++ b/test/specs/commonmark/commonmark.0.29.json @@ -3939,8 +3939,7 @@ "example": 486, "start_line": 7543, "end_line": 7547, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[link](foo\nbar)\n", @@ -3964,8 +3963,7 @@ "example": 489, "start_line": 7571, "end_line": 7575, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[link]()\n", @@ -3973,8 +3971,7 @@ "example": 490, "start_line": 7579, "end_line": 7583, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[a](\n[a](c)\n", @@ -3982,8 +3979,7 @@ "example": 491, "start_line": 7588, "end_line": 7596, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[link](\\(foo\\))\n", diff --git a/test/specs/gfm/commonmark.0.29.json b/test/specs/gfm/commonmark.0.29.json index 7732f6a9da..0526dbf1d3 100644 --- a/test/specs/gfm/commonmark.0.29.json +++ b/test/specs/gfm/commonmark.0.29.json @@ -3939,8 +3939,7 @@ "example": 486, "start_line": 7543, "end_line": 7547, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[link](foo\nbar)\n", @@ -3964,8 +3963,7 @@ "example": 489, "start_line": 7571, "end_line": 7575, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[link]()\n", @@ -3973,8 +3971,7 @@ "example": 490, "start_line": 7579, "end_line": 7583, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[a](\n[a](c)\n", @@ -3982,8 +3979,7 @@ "example": 491, "start_line": 7588, "end_line": 7596, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "[link](\\(foo\\))\n", diff --git a/test/specs/new/link_lt.html b/test/specs/new/link_lt.html index ea48caa950..bb32e53966 100644 --- a/test/specs/new/link_lt.html +++ b/test/specs/new/link_lt.html @@ -1 +1,3 @@ -

URL

+

URL

+ +

URL

diff --git a/test/specs/new/link_lt.md b/test/specs/new/link_lt.md index f4f9adddc9..bfbe71c1b4 100644 --- a/test/specs/new/link_lt.md +++ b/test/specs/new/link_lt.md @@ -1 +1,6 @@ +--- +pedantic: true +--- [URL]()