diff --git a/lib/marked.js b/lib/marked.js index 8012063037..74c45c7d9f 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -554,9 +554,68 @@ inline.normal = merge({}, inline); inline.pedantic = merge({}, inline.normal, { strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, - link: edit(/^!?\[(label)\]\(\s*?(?:\s+(['"][\s\S]*?['"]))?\s*\)/) - .replace('label', inline._label) - .getRegex(), + /* Original link re: /^!?\[(label)\]\(\s*?(?:\s+(['"][\s\S]*?['"]))?\s*\)/ + * This captures the spec reasonably well but is vulnerable to REDOS. + * Instead we use a custom parser that follows the RegExp.exec semantics. */ + link: { + exec: function (s) { + // [TEXT](DESTINATION) + var generalLinkRe = edit(/^!?\[(label)\]\((.*?)\)/) + .replace('label', inline._label) + .getRegex(); + + // destination: DESTINATION from generalLinkRe + // returns [destination, title]: no angle-brackets on destination, no quotes on title + function splitIntoDestinationAndTitle (destination) { + function unwrapAngleBrackets (str) { + if (str.match(/^<.*>$/)) { + str = str.slice(1, -1); + } + return str; + } + + // Valid DESTINATIONs, in decreasing specificity. + var destinationAndTitleRe = /^([^'"(]*[^\s])\s+(['"(].*['")])/; + var destinationRe = /^(?)/; + var parsingRegexes = [destinationAndTitleRe, destinationRe]; + + var match = false; + for (var i = 0; i < parsingRegexes.length; i++) { + match = parsingRegexes[i].exec(destination); + if (match) { + break; + } + } + + if (!match) { + return null; + } + + var dest = match[1]; + var title = match[2] || ''; // Not all parsingRegexes have 2 groups. + + // Format dest. + dest = dest.trim(); + dest = unwrapAngleBrackets(dest); + + return [dest, title]; + } + + var fullMatch = generalLinkRe.exec(s); + if (!fullMatch) { + return null; + } + + var text = fullMatch[1]; + var destination = fullMatch[2]; + + var destinationAndTitle = splitIntoDestinationAndTitle(destination); + if (!destinationAndTitle) { + return null; + } + return [fullMatch[0], text, destinationAndTitle[0], destinationAndTitle[1]]; + } + }, reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/) .replace('label', inline._label) .getRegex()