From 0e07a9f9841af19c7e7596f099818c766df51afd Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Mon, 16 Apr 2018 18:48:20 -0400 Subject: [PATCH 1/9] security: finish fixing unsafe heading regex Apply similar patch to a similar heading regex. Follow-on to f052a2c04. Test: Add a test case to demonstrate the slower blow-up. --- lib/marked.js | 4 +++- test/new/redos_heading.html | 2 ++ test/new/redos_heading.md | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 test/new/redos_heading.html create mode 100644 test/new/redos_heading.md diff --git a/lib/marked.js b/lib/marked.js index 55b06b4aff..8befd9c2ff 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -16,6 +16,7 @@ var block = { code: /^( {4}[^\n]+\n*)+/, fences: noop, hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, + // TODO REDOS: Replace ' *([^\n]+?) *' with dedicated parsing. heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, nptable: noop, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, @@ -93,7 +94,8 @@ block.normal = merge({}, block); block.gfm = merge({}, block.normal, { fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, paragraph: /^/, - heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ + // TODO REDOS: Replace ' *([^\n]+?) *' with dedicated parsing. + heading: /^ *(#{1,6}) +([^\n]+?) *(?:#+ *)?(?:\n+|$)/ }); block.gfm.paragraph = edit(block.paragraph) diff --git a/test/new/redos_heading.html b/test/new/redos_heading.html new file mode 100644 index 0000000000..4ee5794006 --- /dev/null +++ b/test/new/redos_heading.html @@ -0,0 +1,2 @@ +

# A

+ diff --git a/test/new/redos_heading.md b/test/new/redos_heading.md new file mode 100644 index 0000000000..9a01cd01ae --- /dev/null +++ b/test/new/redos_heading.md @@ -0,0 +1 @@ +# # A From 990b45253df2a386cb2a9bae0658b1615ec2663e Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Tue, 17 Apr 2018 22:20:11 -0400 Subject: [PATCH 2/9] security: corrected patch of unsafe heading regex --- lib/marked.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index 8befd9c2ff..28f25ae6e6 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -16,8 +16,8 @@ var block = { code: /^( {4}[^\n]+\n*)+/, fences: noop, hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, - // TODO REDOS: Replace ' *([^\n]+?) *' with dedicated parsing. - heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/, + // cap[2] might be ' HEADING # ' and must be trimmed appropriately. + heading: /^ *(#{1,6})([^\n]*)(?:\n+|$)/, nptable: noop, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, @@ -94,8 +94,8 @@ block.normal = merge({}, block); block.gfm = merge({}, block.normal, { fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, paragraph: /^/, - // TODO REDOS: Replace ' *([^\n]+?) *' with dedicated parsing. - heading: /^ *(#{1,6}) +([^\n]+?) *(?:#+ *)?(?:\n+|$)/ + // cap[2] might be ' HEADING # ' and must be trimmed appropriately. + heading: /^ *(#{1,6}) ([^\n]+)(?:\n+|$)/ }); block.gfm.paragraph = edit(block.paragraph) @@ -237,10 +237,18 @@ Lexer.prototype.token = function(src, top) { // heading if (cap = this.rules.heading.exec(src)) { src = src.substring(cap[0].length); + // cap[2] might be ' HEADING # ' + item = cap[2].trim(); + if (item.slice(-1) === '#') { + // NB replace(/#+$/) is quadratic on mismatch because it's unanchored, + // so we protect with if-check to ensure it won't mismatch. + item = item.replace(/#+$/, ''); + } + item = item.trim() this.tokens.push({ type: 'heading', depth: cap[1].length, - text: cap[2] + text: item }); continue; } From 573601413961d1560b2f5fc874924aee0d94b412 Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Tue, 17 Apr 2018 22:30:14 -0400 Subject: [PATCH 3/9] address review comment: equivalence of [^\n] and . --- lib/marked.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index 28f25ae6e6..a736dc285a 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -17,7 +17,7 @@ var block = { fences: noop, hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, // cap[2] might be ' HEADING # ' and must be trimmed appropriately. - heading: /^ *(#{1,6})([^\n]*)(?:\n+|$)/, + heading: /^ *(#{1,6})(.*)(?:\n+|$)/, nptable: noop, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, @@ -95,7 +95,7 @@ block.gfm = merge({}, block.normal, { fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, paragraph: /^/, // cap[2] might be ' HEADING # ' and must be trimmed appropriately. - heading: /^ *(#{1,6}) ([^\n]+)(?:\n+|$)/ + heading: /^ *(#{1,6}) (.+)(?:\n+|$)/ }); block.gfm.paragraph = edit(block.paragraph) From 943d995e38219116c6490730442dbd36c30b886f Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Wed, 18 Apr 2018 07:55:45 -0500 Subject: [PATCH 4/9] make header up to spec --- lib/marked.js | 18 +++++++++++------- test/new/nogfm_hashtag.md | 2 +- test/specs/commonmark/commonmark-spec.js | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index a736dc285a..942280d037 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -17,7 +17,7 @@ var block = { fences: noop, hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/, // cap[2] might be ' HEADING # ' and must be trimmed appropriately. - heading: /^ *(#{1,6})(.*)(?:\n+|$)/, + heading: /^ {0,3}(#{1,6})(?:[^\S\n](.*))?(?:\n+|$)/, nptable: noop, blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, @@ -93,9 +93,7 @@ block.normal = merge({}, block); block.gfm = merge({}, block.normal, { fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\n? *\1 *(?:\n+|$)/, - paragraph: /^/, - // cap[2] might be ' HEADING # ' and must be trimmed appropriately. - heading: /^ *(#{1,6}) (.+)(?:\n+|$)/ + paragraph: /^/ }); block.gfm.paragraph = edit(block.paragraph) @@ -118,6 +116,7 @@ block.tables = merge({}, block.gfm, { */ block.pedantic = merge({}, block.normal, { + heading: /^ *(#{1,6})(.*)(?:\n+|$)/, html: edit( '^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)' // closed tag @@ -238,13 +237,18 @@ Lexer.prototype.token = function(src, top) { if (cap = this.rules.heading.exec(src)) { src = src.substring(cap[0].length); // cap[2] might be ' HEADING # ' - item = cap[2].trim(); + item = (cap[2] || '').trim(); if (item.slice(-1) === '#') { // NB replace(/#+$/) is quadratic on mismatch because it's unanchored, // so we protect with if-check to ensure it won't mismatch. - item = item.replace(/#+$/, ''); + if (this.options.pedantic) { + item = item.replace(/#+$/, ''); + } else { + // CM requires a space before additional #s + item = item.replace(/(\s|^)#+$/, ''); + } } - item = item.trim() + item = item.trim(); this.tokens.push({ type: 'heading', depth: cap[1].length, diff --git a/test/new/nogfm_hashtag.md b/test/new/nogfm_hashtag.md index 4b805db481..661935470e 100644 --- a/test/new/nogfm_hashtag.md +++ b/test/new/nogfm_hashtag.md @@ -1,5 +1,5 @@ --- -gfm: false +pedantic: true --- #header diff --git a/test/specs/commonmark/commonmark-spec.js b/test/specs/commonmark/commonmark-spec.js index 280824f1c0..90fb25b5c7 100644 --- a/test/specs/commonmark/commonmark-spec.js +++ b/test/specs/commonmark/commonmark-spec.js @@ -110,7 +110,7 @@ describe('CommonMark 0.28 ATX headings', function() { var section = 'ATX headings'; // var shouldPassButFails = []; - var shouldPassButFails = [40, 45, 46, 49]; + var shouldPassButFails = [40]; var willNotBeAttemptedByCoreTeam = []; From 0cfe39ec2db1e4362f5d004d4e955648d95b501b Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Wed, 25 Apr 2018 20:03:48 -0400 Subject: [PATCH 5/9] security: replace unsafe /X+$/ idiom with rtrim Problem: replace(/X+$/, '') is vulnerable to REDOS Solution: Replace all instances I could find with a custom rtrim --- lib/marked.js | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index 942280d037..a06ec295c5 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -4,6 +4,38 @@ * https://github.com/markedjs/marked */ +// Return str with all trailing {c | all but c} removed +// allButC: Default false +function rtrim(str, c, allButC) { + if (typeof allButC === 'undefined') { + allButC = false; + } else { + allButC = true; + } + var mustMatchC = !allButC; + + if (str.length === 0) { + return ''; + } + + // ix+1 of leftmost that fits description + // i.e. the length of the string we should return + var curr = str.length; + + while (curr > 0) { + var currChar = str.charAt(curr - 1); + if (mustMatchC && currChar === c) { + curr--; + } else if (!mustMatchC && currChar !== c) { + curr--; + } else { + break; + } + } + + return str.substr(0, curr); +} + ;(function(root) { 'use strict'; @@ -216,7 +248,7 @@ Lexer.prototype.token = function(src, top) { this.tokens.push({ type: 'code', text: !this.options.pedantic - ? cap.replace(/\n+$/, '') + ? rtrim(cap, '\n') : cap }); continue; @@ -238,15 +270,11 @@ Lexer.prototype.token = function(src, top) { src = src.substring(cap[0].length); // cap[2] might be ' HEADING # ' item = (cap[2] || '').trim(); - if (item.slice(-1) === '#') { - // NB replace(/#+$/) is quadratic on mismatch because it's unanchored, - // so we protect with if-check to ensure it won't mismatch. - if (this.options.pedantic) { - item = item.replace(/#+$/, ''); - } else { - // CM requires a space before additional #s - item = item.replace(/(\s|^)#+$/, ''); - } + if (this.options.pedantic) { + item = rtrim(item, '#'); + } else { + // CM requires a space before additional #s + item = item.replace(/(\s|^)#+$/, ''); } item = item.trim(); this.tokens.push({ @@ -1288,7 +1316,7 @@ function resolveUrl(base, href) { if (/^[^:]+:\/*[^/]*$/.test(base)) { baseUrls[' ' + base] = base + '/'; } else { - baseUrls[' ' + base] = base.replace(/[^/]*$/, ''); + baseUrls[' ' + base] = rtrim(base, '/', true); } } base = baseUrls[' ' + base]; From 29be5f5e61ce69f8fb938d898ed3693fc17e1da0 Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Wed, 25 Apr 2018 21:38:50 -0400 Subject: [PATCH 6/9] WIP: safening the ILG text regex Help wanted. --- lib/marked.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index a06ec295c5..48091177a7 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -4,6 +4,15 @@ * https://github.com/markedjs/marked */ +var NEW_TEXT = true; + +var doLog = true; +function log(msg) { + if (doLog) { + console.log(msg); + } +} + // Return str with all trailing {c | all but c} removed // allButC: Default false function rtrim(str, c, allButC) { @@ -552,6 +561,14 @@ var inline = { text: /^[\s\S]+?(?=[\\?@\[\]\\^_`{|}~])/g; inline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/; @@ -818,11 +835,22 @@ InlineLexer.prototype.output = function(src) { } // text + log(`lexer: Matching text: ${this.rules.text.source}\n <${src}>`); if (cap = this.rules.text.exec(src)) { - src = src.substring(cap[0].length); - out += this.renderer.text(escape(this.smartypants(cap[0]))); - continue; + if (NEW_TEXT) { + log(`lexer: Match: ${cap} ${cap.index}`); + var textLen = cap.index + 1; + // text is not in cap[0], so extract text before advancing src. + out += this.renderer.text(escape(this.smartypants(src.substr(0, textLen)))); + src = src.substring(textLen); + continue; + } else { + src = src.substring(cap[0].length); + out += this.renderer.text(escape(this.smartypants(cap[0]))); + continue; + } } + log(`lexer: Mismatch`); if (src) { throw new Error('Infinite loop on byte: ' + src.charCodeAt(0)); From 4d5cfc74d21def9251359192c9c2a2ff98bd0054 Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Thu, 26 Apr 2018 18:07:32 -0400 Subject: [PATCH 7/9] address review comments: relocate rtrim --- lib/marked.js | 69 ++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index 48091177a7..fde8f12231 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -4,47 +4,15 @@ * https://github.com/markedjs/marked */ -var NEW_TEXT = true; +var NEW_TEXT = false; -var doLog = true; +var doLog = false; function log(msg) { if (doLog) { console.log(msg); } } -// Return str with all trailing {c | all but c} removed -// allButC: Default false -function rtrim(str, c, allButC) { - if (typeof allButC === 'undefined') { - allButC = false; - } else { - allButC = true; - } - var mustMatchC = !allButC; - - if (str.length === 0) { - return ''; - } - - // ix+1 of leftmost that fits description - // i.e. the length of the string we should return - var curr = str.length; - - while (curr > 0) { - var currChar = str.charAt(curr - 1); - if (mustMatchC && currChar === c) { - curr--; - } else if (!mustMatchC && currChar !== c) { - curr--; - } else { - break; - } - } - - return str.substr(0, curr); -} - ;(function(root) { 'use strict'; @@ -1390,6 +1358,39 @@ function splitCells(tableRow) { return cells; } +// Return str with all trailing {c | all but c} removed +// allButC: Default false +function rtrim(str, c, allButC) { + if (typeof allButC === 'undefined') { + allButC = false; + } else { + allButC = true; + } + var mustMatchC = !allButC; + + if (str.length === 0) { + return ''; + } + + // ix+1 of leftmost that fits description + // i.e. the length of the string we should return + var curr = str.length; + + while (curr > 0) { + var currChar = str.charAt(curr - 1); + if (mustMatchC && currChar === c) { + curr--; + } else if (!mustMatchC && currChar !== c) { + curr--; + } else { + break; + } + } + + return str.substr(0, curr); +} + + /** * Marked */ From dd26af8d50098a6604513930a4081d9d7dd6a36f Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Thu, 26 Apr 2018 19:47:10 -0400 Subject: [PATCH 8/9] remove leading whitespace in paragraphs Spec: No leading whitespace within a paragraph Fix: I strip leading whitespace on each line while rendering paragraphs Unanticipated problem: Causes the (previously failing but hidden) CommonMark 318 to fail. I added that to the list of expected-to-fail tests. This commit addresses a failing header test case noted in 943d995e38 whose root cause was paragraph rendering not up to spec. --- lib/marked.js | 10 +++++++--- test/specs/commonmark/commonmark-spec.js | 16 ++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index fde8f12231..8256f0283b 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -534,7 +534,7 @@ if (NEW_TEXT) { // we break the definition of GFM inline.breaks further down (affects the gfm_break test). // Furthermore, we still have trouble with the email pattern substituted in: /|[...]+@/, which // is vulnerable to REDOS just like /| {2,}\n/ was - inline.text = /[\s\S](?:[\\?@\[\]\\^_`{|}~])/g; @@ -961,7 +961,12 @@ Renderer.prototype.listitem = function(text) { }; Renderer.prototype.paragraph = function(text) { - return '

' + text + '

\n'; + var lines = text.split(/\n/); + lines = lines.map(function(l) { + return l.replace(/^ +/, ''); + }); + + return '

' + lines.join('\n') + '

\n'; }; Renderer.prototype.table = function(header, body) { @@ -1390,7 +1395,6 @@ function rtrim(str, c, allButC) { return str.substr(0, curr); } - /** * Marked */ diff --git a/test/specs/commonmark/commonmark-spec.js b/test/specs/commonmark/commonmark-spec.js index 90fb25b5c7..1210884a4c 100644 --- a/test/specs/commonmark/commonmark-spec.js +++ b/test/specs/commonmark/commonmark-spec.js @@ -94,8 +94,7 @@ describe('CommonMark 0.28 Precedence', function() { describe('CommonMark 0.28 Thematic breaks', function() { var section = 'Thematic breaks'; - // var shouldPassButFails = []; - var shouldPassButFails = [19]; + var shouldPassButFails = []; var willNotBeAttemptedByCoreTeam = []; @@ -109,8 +108,7 @@ describe('CommonMark 0.28 Thematic breaks', function() { describe('CommonMark 0.28 ATX headings', function() { var section = 'ATX headings'; - // var shouldPassButFails = []; - var shouldPassButFails = [40]; + var shouldPassButFails = []; var willNotBeAttemptedByCoreTeam = []; @@ -139,8 +137,7 @@ describe('CommonMark 0.28 Setext headings', function() { describe('CommonMark 0.28 Indented code blocks', function() { var section = 'Indented code blocks'; - // var shouldPassButFails = []; - var shouldPassButFails = [82]; + var shouldPassButFails = []; var willNotBeAttemptedByCoreTeam = []; @@ -198,8 +195,7 @@ describe('CommonMark 0.28 Link reference definitions', function() { describe('CommonMark 0.28 Paragraphs', function() { var section = 'Paragraphs'; - // var shouldPassButFails = []; - var shouldPassButFails = [185, 186]; + var shouldPassButFails = []; var willNotBeAttemptedByCoreTeam = []; @@ -319,7 +315,7 @@ describe('CommonMark 0.28 Code spans', function() { var section = 'Code spans'; // var shouldPassButFails = []; - var shouldPassButFails = [330, 316, 328, 320, 323, 322]; + var shouldPassButFails = [330, 316, 318, 328, 320, 323, 322]; var willNotBeAttemptedByCoreTeam = []; @@ -349,7 +345,7 @@ describe('CommonMark 0.28 Links', function() { var section = 'Links'; // var shouldPassButFails = []; - var shouldPassButFails = [468, 474, 478, 483, 489, 490, 491, 492, 495, 496, 497, 499, 503, 504, 505, 507, 508, 509, 523, 535]; + var shouldPassButFails = [468, 474, 478, 483, 489, 490, 491, 492, 495, 496, 497, 499, 503, 504, 505, 507, 508, 509, 535]; var willNotBeAttemptedByCoreTeam = []; From 24d4a5efb3653a676646750a67c1195959e27f93 Mon Sep 17 00:00:00 2001 From: Jamie Davis Date: Fri, 27 Apr 2018 22:29:21 -0400 Subject: [PATCH 9/9] WIP: safen the text regex via linear-time scans Sketch implementing text regex as a linear-time RegExp imitator. - A few nits here and there - I haven't tested all of the offsetOfX routines, so 'npm run test' hangs on some bug --- lib/marked.js | 164 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 150 insertions(+), 14 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index 8256f0283b..f2e5090e0d 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -4,7 +4,7 @@ * https://github.com/markedjs/marked */ -var NEW_TEXT = false; +var NEW_TEXT = true; var doLog = false; function log(msg) { @@ -526,15 +526,146 @@ var inline = { code: /^(`+)\s*([\s\S]*?[^`]?)\s*\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, del: noop, - text: /^[\s\S]+?(?=[\\?@\[\]\\^_`{|}~])/g; @@ -599,10 +730,7 @@ inline.gfm = merge({}, inline.normal, { .getRegex(), _backpedal: /(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/, del: /^~~(?=\S)([\s\S]*?\S)~~/, - text: edit(inline.text) - .replace(']|', '~]|') - .replace('|', '|https?://|ftp://|www\\.|[a-zA-Z0-9.!#$%&\'*+/=?^_`{\\|}~-]+@|') - .getRegex() + text: { exec: offsetOfTextBreakInlineGFM } // TODO Missing: .replace(']|', '~]|') }); /** @@ -611,7 +739,7 @@ inline.gfm = merge({}, inline.normal, { inline.breaks = merge({}, inline.gfm, { br: edit(inline.br).replace('{2,}', '*').getRegex(), - text: edit(inline.gfm.text).replace('{2,}', '*').getRegex() + text: { exec: offsetOfTextBreakInlineGFM } // TODO Missing: inline.gfm.text.replace('{2,}', '*') }); /** @@ -803,16 +931,22 @@ InlineLexer.prototype.output = function(src) { } // text - log(`lexer: Matching text: ${this.rules.text.source}\n <${src}>`); + //log(`lexer: Matching text: ${this.rules.text.source}\n <${src}>`); if (cap = this.rules.text.exec(src)) { if (NEW_TEXT) { - log(`lexer: Match: ${cap} ${cap.index}`); + log(`lexer: Match: ${JSON.stringify(cap)} ${cap.index}`); var textLen = cap.index + 1; // text is not in cap[0], so extract text before advancing src. out += this.renderer.text(escape(this.smartypants(src.substr(0, textLen)))); src = src.substring(textLen); continue; } else { + var offInline = offsetOfTextBreakInline(src); + var offInlineGFM = offsetOfTextBreakInlineGFM(src); + console.log(`cap ${JSON.stringify(cap)}`); + console.log(`offInline ${JSON.stringify(offInline)}`); + console.log(`offInlineGFM ${JSON.stringify(offInlineGFM)}`); + console.log(`regex ${cap[0].length} offInline ${offInline[0].length} offInlineGFM ${offInlineGFM[0].length}`); src = src.substring(cap[0].length); out += this.renderer.text(escape(this.smartypants(cap[0]))); continue; @@ -1530,6 +1664,8 @@ marked.defaults = marked.getDefaults(); * Expose */ +marked(' # # ####A'); + marked.Parser = Parser; marked.parser = Parser.parse;