From 678f87ed45c0f18a76ad19fa9de555e77dea78a8 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 15 Jun 2018 14:51:18 -0700 Subject: [PATCH] Update marked --- src/vs/base/common/marked/marked.js | 266 +++++++++++++++++----------- 1 file changed, 166 insertions(+), 100 deletions(-) diff --git a/src/vs/base/common/marked/marked.js b/src/vs/base/common/marked/marked.js index b20380f19b7..64cc685b4c2 100644 --- a/src/vs/base/common/marked/marked.js +++ b/src/vs/base/common/marked/marked.js @@ -35,7 +35,7 @@ var block = { def: /^ {0,3}\[(label)\]: *\n? *]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/, table: noop, lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, - paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)+)/, + paragraph: /^([^\n]+(?:\n(?!hr|heading|lheading| {0,3}>|<\/?(?:tag)(?: +|\n|\/?>)|<(?:script|pre|style|!--))[^\n]+)*)/, text: /^[^\n]+/ }; @@ -162,7 +162,7 @@ Lexer.rules = block; * Static Lex Method */ -Lexer.lex = function (src, options) { +Lexer.lex = function(src, options) { var lexer = new Lexer(options); return lexer.lex(src); }; @@ -171,7 +171,7 @@ Lexer.lex = function (src, options) { * Preprocessing */ -Lexer.prototype.lex = function (src) { +Lexer.prototype.lex = function(src) { src = src .replace(/\r\n|\r/g, '\n') .replace(/\t/g, ' ') @@ -185,19 +185,21 @@ Lexer.prototype.lex = function (src) { * Lexing */ -Lexer.prototype.token = function (src, top) { +Lexer.prototype.token = function(src, top) { src = src.replace(/^ +$/gm, ''); var next, - loose, - cap, - bull, - b, - item, - space, - i, - tag, - l, - isordered; + loose, + cap, + bull, + b, + item, + space, + i, + tag, + l, + isordered, + istask, + ischecked; while (src) { // newline @@ -217,7 +219,7 @@ Lexer.prototype.token = function (src, top) { this.tokens.push({ type: 'code', text: !this.options.pedantic - ? cap.replace(/\n+$/, '') + ? rtrim(cap, '\n') : cap }); continue; @@ -365,10 +367,20 @@ Lexer.prototype.token = function (src, top) { if (!loose) loose = next; } + // Check for task list items + istask = /^\[[ xX]\] /.test(item); + ischecked = undefined; + if (istask) { + ischecked = item[1] !== ' '; + item = item.replace(/^\[[ xX]\] +/, ''); + } + this.tokens.push({ type: loose ? 'loose_item_start' - : 'list_item_start' + : 'list_item_start', + task: istask, + checked: ischecked }); // Recurse. @@ -630,7 +642,7 @@ InlineLexer.rules = inline; * Static Lexing/Compiling Method */ -InlineLexer.output = function (src, links, options) { +InlineLexer.output = function(src, links, options) { var inline = new InlineLexer(links, options); return inline.output(src); }; @@ -639,13 +651,13 @@ InlineLexer.output = function (src, links, options) { * Lexing/Compiling */ -InlineLexer.prototype.output = function (src) { +InlineLexer.prototype.output = function(src) { var out = '', - link, - text, - href, - title, - cap; + link, + text, + href, + title, + cap; while (src) { // escape @@ -732,7 +744,7 @@ InlineLexer.prototype.output = function (src) { // reflink, nolink if ((cap = this.rules.reflink.exec(src)) - || (cap = this.rules.nolink.exec(src))) { + || (cap = this.rules.nolink.exec(src))) { src = src.substring(cap[0].length); link = (cap[2] || cap[1]).replace(/\s+/g, ' '); link = this.links[link.toLowerCase()]; @@ -797,7 +809,7 @@ InlineLexer.prototype.output = function (src) { return out; }; -InlineLexer.escapes = function (text) { +InlineLexer.escapes = function(text) { return text ? text.replace(InlineLexer.rules._escapes, '$1') : text; } @@ -805,9 +817,9 @@ InlineLexer.escapes = function (text) { * Compile Link */ -InlineLexer.prototype.outputLink = function (cap, link) { +InlineLexer.prototype.outputLink = function(cap, link) { var href = link.href, - title = link.title ? escape(link.title) : null; + title = link.title ? escape(link.title) : null; return cap[0].charAt(0) !== '!' ? this.renderer.link(href, title, this.output(cap[1])) @@ -818,7 +830,7 @@ InlineLexer.prototype.outputLink = function (cap, link) { * Smartypants Transformations */ -InlineLexer.prototype.smartypants = function (text) { +InlineLexer.prototype.smartypants = function(text) { if (!this.options.smartypants) return text; return text // em-dashes @@ -841,12 +853,12 @@ InlineLexer.prototype.smartypants = function (text) { * Mangle Links */ -InlineLexer.prototype.mangle = function (text) { +InlineLexer.prototype.mangle = function(text) { if (!this.options.mangle) return text; var out = '', - l = text.length, - i = 0, - ch; + l = text.length, + i = 0, + ch; for (; i < l; i++) { ch = text.charCodeAt(i); @@ -867,7 +879,7 @@ function Renderer(options) { this.options = options || marked.defaults; } -Renderer.prototype.code = function (code, lang, escaped) { +Renderer.prototype.code = function(code, lang, escaped) { if (this.options.highlight) { var out = this.options.highlight(code, lang); if (out != null && out !== code) { @@ -879,7 +891,7 @@ Renderer.prototype.code = function (code, lang, escaped) { if (!lang) { return '
'
       + (escaped ? code : escape(code, true))
-      + '\n
'; + + ''; } return '
'
     + (escaped ? code : escape(code, true))
-    + '\n
\n'; + + '\n'; }; -Renderer.prototype.blockquote = function (quote) { +Renderer.prototype.blockquote = function(quote) { return '
\n' + quote + '
\n'; }; -Renderer.prototype.html = function (html) { +Renderer.prototype.html = function(html) { return html; }; -Renderer.prototype.heading = function (text, level, raw) { +Renderer.prototype.heading = function(text, level, raw) { if (this.options.headerIds) { return '' + text + '\n'; }; -Renderer.prototype.hr = function () { +Renderer.prototype.hr = function() { return this.options.xhtml ? '
\n' : '
\n'; }; -Renderer.prototype.list = function (body, ordered, start) { +Renderer.prototype.list = function(body, ordered, start) { var type = ordered ? 'ol' : 'ul', - startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; + startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''; return '<' + type + startatt + '>\n' + body + '\n'; }; -Renderer.prototype.listitem = function (text) { +Renderer.prototype.listitem = function(text) { return '
  • ' + text + '
  • \n'; }; -Renderer.prototype.paragraph = function (text) { +Renderer.prototype.checkbox = function(checked) { + return ' '; +} + +Renderer.prototype.paragraph = function(text) { return '

    ' + text + '

    \n'; }; -Renderer.prototype.table = function (header, body) { +Renderer.prototype.table = function(header, body) { if (body) body = '' + body + ''; return '\n' @@ -944,11 +964,11 @@ Renderer.prototype.table = function (header, body) { + '
    \n'; }; -Renderer.prototype.tablerow = function (content) { +Renderer.prototype.tablerow = function(content) { return '\n' + content + '\n'; }; -Renderer.prototype.tablecell = function (content, flags) { +Renderer.prototype.tablecell = function(content, flags) { var type = flags.header ? 'th' : 'td'; var tag = flags.align ? '<' + type + ' align="' + flags.align + '">' @@ -957,27 +977,27 @@ Renderer.prototype.tablecell = function (content, flags) { }; // span level renderer -Renderer.prototype.strong = function (text) { +Renderer.prototype.strong = function(text) { return '' + text + ''; }; -Renderer.prototype.em = function (text) { +Renderer.prototype.em = function(text) { return '' + text + ''; }; -Renderer.prototype.codespan = function (text) { +Renderer.prototype.codespan = function(text) { return '' + text + ''; }; -Renderer.prototype.br = function () { +Renderer.prototype.br = function() { return this.options.xhtml ? '
    ' : '
    '; }; -Renderer.prototype.del = function (text) { +Renderer.prototype.del = function(text) { return '' + text + ''; }; -Renderer.prototype.link = function (href, title, text) { +Renderer.prototype.link = function(href, title, text) { if (this.options.sanitize) { try { var prot = decodeURIComponent(unescape(href)) @@ -1006,7 +1026,7 @@ Renderer.prototype.link = function (href, title, text) { return out; }; -Renderer.prototype.image = function (href, title, text) { +Renderer.prototype.image = function(href, title, text) { if (this.options.baseUrl && !originIndependentUrl.test(href)) { href = resolveUrl(this.options.baseUrl, href); } @@ -1018,7 +1038,7 @@ Renderer.prototype.image = function (href, title, text) { return out; }; -Renderer.prototype.text = function (text) { +Renderer.prototype.text = function(text) { return text; }; @@ -1027,24 +1047,24 @@ Renderer.prototype.text = function (text) { * returns only the textual part of the token */ -function TextRenderer() { } +function TextRenderer() {} // no need for block level renderers TextRenderer.prototype.strong = - TextRenderer.prototype.em = - TextRenderer.prototype.codespan = - TextRenderer.prototype.del = - TextRenderer.prototype.text = function (text) { - return text; - } +TextRenderer.prototype.em = +TextRenderer.prototype.codespan = +TextRenderer.prototype.del = +TextRenderer.prototype.text = function (text) { + return text; +} TextRenderer.prototype.link = - TextRenderer.prototype.image = function (href, title, text) { - return '' + text; - } +TextRenderer.prototype.image = function(href, title, text) { + return '' + text; +} -TextRenderer.prototype.br = function () { +TextRenderer.prototype.br = function() { return ''; } @@ -1065,7 +1085,7 @@ function Parser(options) { * Static Parse Method */ -Parser.parse = function (src, options) { +Parser.parse = function(src, options) { var parser = new Parser(options); return parser.parse(src); }; @@ -1074,12 +1094,12 @@ Parser.parse = function (src, options) { * Parse Loop */ -Parser.prototype.parse = function (src) { +Parser.prototype.parse = function(src) { this.inline = new InlineLexer(src.links, this.options); // use an InlineLexer with a TextRenderer to extract pure text this.inlineText = new InlineLexer( src.links, - merge({}, this.options, { renderer: new TextRenderer() }) + merge({}, this.options, {renderer: new TextRenderer()}) ); this.tokens = src.reverse(); @@ -1095,7 +1115,7 @@ Parser.prototype.parse = function (src) { * Next Token */ -Parser.prototype.next = function () { +Parser.prototype.next = function() { return this.token = this.tokens.pop(); }; @@ -1103,7 +1123,7 @@ Parser.prototype.next = function () { * Preview Next Token */ -Parser.prototype.peek = function () { +Parser.prototype.peek = function() { return this.tokens[this.tokens.length - 1] || 0; }; @@ -1111,7 +1131,7 @@ Parser.prototype.peek = function () { * Parse Text Tokens */ -Parser.prototype.parseText = function () { +Parser.prototype.parseText = function() { var body = this.token.text; while (this.peek().type === 'text') { @@ -1125,7 +1145,7 @@ Parser.prototype.parseText = function () { * Parse Current Token */ -Parser.prototype.tok = function () { +Parser.prototype.tok = function() { switch (this.token.type) { case 'space': { return ''; @@ -1146,11 +1166,11 @@ Parser.prototype.tok = function () { } case 'table': { var header = '', - body = '', - i, - row, - cell, - j; + body = '', + i, + row, + cell, + j; // header cell = ''; @@ -1189,7 +1209,7 @@ Parser.prototype.tok = function () { case 'list_start': { body = ''; var ordered = this.token.ordered, - start = this.token.start; + start = this.token.start; while (this.next().type !== 'list_end') { body += this.tok(); @@ -1200,6 +1220,10 @@ Parser.prototype.tok = function () { case 'list_item_start': { body = ''; + if (this.token.task) { + body += this.renderer.checkbox(this.token.checked); + } + while (this.next().type !== 'list_item_end') { body += this.token.type === 'text' ? this.parseText() @@ -1245,7 +1269,7 @@ function escape(html, encode) { function unescape(html) { // explicitly match decimal, hex, and named HTML entities - return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function (_, n) { + return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) { n = n.toLowerCase(); if (n === 'colon') return ':'; if (n.charAt(0) === '#') { @@ -1261,13 +1285,13 @@ function edit(regex, opt) { regex = regex.source || regex; opt = opt || ''; return { - replace: function (name, val) { + replace: function(name, val) { val = val.source || val; val = val.replace(/(^|[^\[])\^/g, '$1'); regex = regex.replace(name, val); return this; }, - getRegex: function () { + getRegex: function() { return new RegExp(regex, opt); } }; @@ -1281,7 +1305,7 @@ function resolveUrl(base, href) { if (/^[^:]+:\/*[^/]*$/.test(base)) { baseUrls[' ' + base] = base + '/'; } else { - baseUrls[' ' + base] = base.replace(/[^/]*$/, ''); + baseUrls[' ' + base] = rtrim(base, '/', true); } } base = baseUrls[' ' + base]; @@ -1297,13 +1321,13 @@ function resolveUrl(base, href) { var baseUrls = {}; var originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; -function noop() { } +function noop() {} noop.exec = noop; function merge(obj) { var i = 1, - target, - key; + target, + key; for (; i < arguments.length; i++) { target = arguments[i]; @@ -1318,8 +1342,23 @@ function merge(obj) { } function splitCells(tableRow, count) { - var cells = tableRow.replace(/([^\\])\|/g, '$1 |').split(/ +\| */), - i = 0; + // ensure that every cell-delimiting pipe has a space + // before it to distinguish it from an escaped pipe + var row = tableRow.replace(/\|/g, function (match, offset, str) { + var escaped = false, + curr = offset; + while (--curr >= 0 && str[curr] === '\\') escaped = !escaped; + if (escaped) { + // odd number of slashes means | is escaped + // so we leave it alone + return '|'; + } else { + // add space before unescaped | + return ' |'; + } + }), + cells = row.split(/ \|/), + i = 0; if (cells.length > count) { cells.splice(count); @@ -1328,11 +1367,38 @@ function splitCells(tableRow, count) { } for (; i < cells.length; i++) { - cells[i] = cells[i].replace(/\\\|/g, '|'); + // leading or trailing whitespace is ignored per the gfm spec + cells[i] = cells[i].trim().replace(/\\\|/g, '|'); } return cells; } +// Remove trailing 'c's. Equivalent to str.replace(/c*$/, ''). +// /c*$/ is vulnerable to REDOS. +// invert: Remove suffix of non-c chars instead. Default falsey. +function rtrim(str, c, invert) { + if (str.length === 0) { + return ''; + } + + // Length of suffix matching the invert condition. + var suffLen = 0; + + // Step left until we fail to match the invert condition. + while (suffLen < str.length) { + var currChar = str.charAt(str.length - suffLen - 1); + if (currChar === c && !invert) { + suffLen++; + } else if (currChar !== c && invert) { + suffLen++; + } else { + break; + } + } + + return str.substr(0, str.length - suffLen); +} + /** * Marked */ @@ -1356,9 +1422,9 @@ function marked(src, opt, callback) { opt = merge({}, marked.defaults, opt || {}); var highlight = opt.highlight, - tokens, - pending, - i = 0; + tokens, + pending, + i = 0; try { tokens = Lexer.lex(src, opt) @@ -1368,7 +1434,7 @@ function marked(src, opt, callback) { pending = tokens.length; - var done = function (err) { + var done = function(err) { if (err) { opt.highlight = highlight; return callback(err); @@ -1398,11 +1464,11 @@ function marked(src, opt, callback) { if (!pending) return done(); for (; i < tokens.length; i++) { - (function (token) { + (function(token) { if (token.type !== 'code') { return --pending || done(); } - return highlight(token.text, token.lang, function (err, code) { + return highlight(token.text, token.lang, function(err, code) { if (err) return done(err); if (code == null || code === token.text) { return --pending || done(); @@ -1435,10 +1501,10 @@ function marked(src, opt, callback) { */ marked.options = - marked.setOptions = function (opt) { - merge(marked.defaults, opt); - return marked; - }; +marked.setOptions = function(opt) { + merge(marked.defaults, opt); + return marked; +}; marked.getDefaults = function () { return { @@ -1448,7 +1514,7 @@ marked.getDefaults = function () { headerIds: true, headerPrefix: '', highlight: null, - langPrefix: 'lang-', + langPrefix: 'language-', mangle: true, pedantic: false, renderer: new Renderer(), @@ -1460,7 +1526,7 @@ marked.getDefaults = function () { tables: true, xhtml: false }; -}; +} marked.defaults = marked.getDefaults(); -- GitLab