Skip to content

Commit 70ee29c

Browse files
authored
fix: fix atx heading and make regex safe (#1853)
1 parent 656c3e4 commit 70ee29c

9 files changed

Lines changed: 78 additions & 13 deletions

File tree

lib/marked.esm.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -435,11 +435,24 @@ var Tokenizer_1 = class Tokenizer {
435435
heading(src) {
436436
const cap = this.rules.block.heading.exec(src);
437437
if (cap) {
438+
let text = cap[2].trim();
439+
440+
// remove trailing #s
441+
if (text.endsWith('#')) {
442+
const trimmed = rtrim$1(text, '#');
443+
if (this.options.pedantic) {
444+
text = trimmed.trim();
445+
} else if (!trimmed || trimmed.endsWith(' ')) {
446+
// CommonMark requires space before trailing #s
447+
text = trimmed.trim();
448+
}
449+
}
450+
438451
return {
439452
type: 'heading',
440453
raw: cap[0],
441454
depth: cap[1].length,
442-
text: cap[2]
455+
text: text
443456
};
444457
}
445458
}
@@ -1003,7 +1016,7 @@ const block = {
10031016
code: /^( {4}[^\n]+\n*)+/,
10041017
fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
10051018
hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
1006-
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,
1019+
heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
10071020
blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
10081021
list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,
10091022
html: '^ {0,3}(?:' // optional indentation
@@ -1134,7 +1147,7 @@ block.pedantic = merge$1({}, block.normal, {
11341147
+ '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
11351148
.getRegex(),
11361149
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
1137-
heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,
1150+
heading: /^(#{1,6})(.*)(?:\n+|$)/,
11381151
fences: noopTest$1, // fences not supported
11391152
paragraph: edit$1(block.normal._paragraph)
11401153
.replace('hr', block.hr)

lib/marked.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -532,11 +532,24 @@
532532
var cap = this.rules.block.heading.exec(src);
533533

534534
if (cap) {
535+
var text = cap[2].trim(); // remove trailing #s
536+
537+
if (text.endsWith('#')) {
538+
var trimmed = rtrim$1(text, '#');
539+
540+
if (this.options.pedantic) {
541+
text = trimmed.trim();
542+
} else if (!trimmed || trimmed.endsWith(' ')) {
543+
// CommonMark requires space before trailing #s
544+
text = trimmed.trim();
545+
}
546+
}
547+
535548
return {
536549
type: 'heading',
537550
raw: cap[0],
538551
depth: cap[1].length,
539-
text: cap[2]
552+
text: text
540553
};
541554
}
542555
};
@@ -1122,7 +1135,7 @@
11221135
code: /^( {4}[^\n]+\n*)+/,
11231136
fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
11241137
hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
1125-
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,
1138+
heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
11261139
blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
11271140
list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,
11281141
html: '^ {0,3}(?:' // optional indentation
@@ -1193,7 +1206,7 @@
11931206
html: edit$1('^ *(?:comment *(?:\\n|\\s*$)' + '|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)' // closed tag
11941207
+ '|<tag(?:"[^"]*"|\'[^\']*\'|\\s[^\'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))').replace('comment', block._comment).replace(/tag/g, '(?!(?:' + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub' + '|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)' + '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b').getRegex(),
11951208
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
1196-
heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,
1209+
heading: /^(#{1,6})(.*)(?:\n+|$)/,
11971210
fences: noopTest$1,
11981211
// fences not supported
11991212
paragraph: edit$1(block.normal._paragraph).replace('hr', block.hr).replace('heading', ' *#{1,6} *[^\n]').replace('lheading', block.lheading).replace('blockquote', ' {0,3}>').replace('|fences', '').replace('|list', '').replace('|html', '').getRegex()

src/Tokenizer.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,24 @@ module.exports = class Tokenizer {
121121
heading(src) {
122122
const cap = this.rules.block.heading.exec(src);
123123
if (cap) {
124+
let text = cap[2].trim();
125+
126+
// remove trailing #s
127+
if (text.endsWith('#')) {
128+
const trimmed = rtrim(text, '#');
129+
if (this.options.pedantic) {
130+
text = trimmed.trim();
131+
} else if (!trimmed || trimmed.endsWith(' ')) {
132+
// CommonMark requires space before trailing #s
133+
text = trimmed.trim();
134+
}
135+
}
136+
124137
return {
125138
type: 'heading',
126139
raw: cap[0],
127140
depth: cap[1].length,
128-
text: cap[2]
141+
text: text
129142
};
130143
}
131144
}

src/rules.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const block = {
1212
code: /^( {4}[^\n]+\n*)+/,
1313
fences: /^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,
1414
hr: /^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,
15-
heading: /^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,
15+
heading: /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,
1616
blockquote: /^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,
1717
list: /^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,
1818
html: '^ {0,3}(?:' // optional indentation
@@ -143,7 +143,7 @@ block.pedantic = merge({}, block.normal, {
143143
+ '\\b)\\w+(?!:|[^\\w\\s@]*@)\\b')
144144
.getRegex(),
145145
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
146-
heading: /^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,
146+
heading: /^(#{1,6})(.*)(?:\n+|$)/,
147147
fences: noopTest, // fences not supported
148148
paragraph: edit(block.normal._paragraph)
149149
.replace('hr', block.hr)

test/specs/commonmark/commonmark.0.29.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,7 @@
389389
"example": 49,
390390
"start_line": 963,
391391
"end_line": 971,
392-
"section": "ATX headings",
393-
"shouldFail": true
392+
"section": "ATX headings"
394393
},
395394
{
396395
"markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n",

test/specs/gfm/commonmark.0.29.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,7 @@
389389
"example": 49,
390390
"start_line": 963,
391391
"end_line": 971,
392-
"section": "ATX headings",
393-
"shouldFail": true
392+
"section": "ATX headings"
394393
},
395394
{
396395
"markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<h1 id="h1">h1</h1>
2+
3+
<h1 id="h1-1">h1</h1>
4+
5+
<h1 id="h1-">h1 #</h1>
6+
7+
<h1 id="h1-2">h1</h1>
8+
9+
<p># h1</p>

test/specs/new/pedantic_heading.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
pedantic: true
3+
---
4+
#h1
5+
6+
#h1#
7+
8+
#h1 # #
9+
10+
#h1####
11+
12+
# h1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
markdown: `# #${' '.repeat(50000)}a`,
3+
html: '<h1># a</h1>',
4+
options: {
5+
headerIds: false
6+
}
7+
};

0 commit comments

Comments
 (0)