Skip to content

Commit cf7cf8f

Browse files
committed
Fix breaking out of code (text) w/ potential flow after eols
Closes GH-13.
1 parent f355cc6 commit cf7cf8f

File tree

4 files changed

+100
-23
lines changed

4 files changed

+100
-23
lines changed

lib/handle/inline-code.js

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
module.exports = inlineCode
22
inlineCode.peek = inlineCodePeek
33

4-
function inlineCode(node) {
4+
var compilePattern = require('../util/compile-pattern')
5+
6+
function inlineCode(node, parent, context) {
57
var value = node.value || ''
68
var sequence = '`'
7-
var pad = ''
9+
var index = -1
10+
var pattern
11+
var expression
12+
var match
13+
var position
814

915
// If there is a single grave accent on its own in the code, use a fence of
1016
// two.
@@ -20,10 +26,42 @@ function inlineCode(node) {
2026
(/[ \r\n`]/.test(value.charAt(0)) ||
2127
/[ \r\n`]/.test(value.charAt(value.length - 1)))
2228
) {
23-
pad = ' '
29+
value = ' ' + value + ' '
30+
}
31+
32+
// We have a potential problem: certain characters after eols could result in
33+
// blocks being seen.
34+
// For example, if someone injected the string `'\n# b'`, then that would
35+
// result in an ATX heading.
36+
// We can’t escape characters in `inlineCode`, but because eols are
37+
// transformed to spaces when going from markdown to HTML anyway, we can swap
38+
// them out.
39+
while (++index < context.unsafe.length) {
40+
pattern = context.unsafe[index]
41+
42+
// Only look for `atBreak`s.
43+
// Btw: note that `atBreak` patterns will always start the regex at LF or
44+
// CR.
45+
if (!pattern.atBreak) continue
46+
47+
expression = compilePattern(pattern)
48+
49+
while ((match = expression.exec(value))) {
50+
position = match.index
51+
52+
// Support CRLF (patterns only look for one of the characters).
53+
if (
54+
value.charCodeAt(position) === 10 /* `\n` */ &&
55+
value.charCodeAt(position - 1) === 13 /* `\r` */
56+
) {
57+
position--
58+
}
59+
60+
value = value.slice(0, position) + ' ' + value.slice(match.index + 1)
61+
}
2462
}
2563

26-
return sequence + pad + value + pad + sequence
64+
return sequence + value + sequence
2765
}
2866

2967
function inlineCodePeek() {

lib/util/compile-pattern.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module.exports = compilePattern
2+
3+
function compilePattern(pattern) {
4+
var before
5+
var after
6+
7+
if (!pattern._compiled) {
8+
before = pattern.before ? '(?:' + pattern.before + ')' : ''
9+
after = pattern.after ? '(?:' + pattern.after + ')' : ''
10+
11+
if (pattern.atBreak) {
12+
before = '[\\r\\n][\\t ]*' + before
13+
}
14+
15+
pattern._compiled = new RegExp(
16+
(before ? '(' + before + ')' : '') +
17+
(/[|\\{}()[\]^$+*?.-]/.test(pattern.character) ? '\\' : '') +
18+
pattern.character +
19+
(after || ''),
20+
'g'
21+
)
22+
}
23+
24+
return pattern._compiled
25+
}

lib/util/safe.js

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module.exports = safe
22

3+
var compilePattern = require('./compile-pattern')
4+
35
function safe(context, input, config) {
46
var value = (config.before || '') + (input || '') + (config.after || '')
57
var positions = []
@@ -25,8 +27,7 @@ function safe(context, input, config) {
2527
continue
2628
}
2729

28-
expression =
29-
pattern._compiled || (pattern._compiled = toExpression(pattern))
30+
expression = compilePattern(pattern)
3031

3132
while ((match = expression.exec(value))) {
3233
before = 'before' in pattern || pattern.atBreak
@@ -126,23 +127,6 @@ function inScope(stack, list, none) {
126127
return false
127128
}
128129

129-
function toExpression(pattern) {
130-
var before = pattern.before ? '(?:' + pattern.before + ')' : ''
131-
var after = pattern.after ? '(?:' + pattern.after + ')' : ''
132-
133-
if (pattern.atBreak) {
134-
before = '[\\r\\n][\\t ]*' + before
135-
}
136-
137-
return new RegExp(
138-
(before ? '(' + before + ')' : '') +
139-
(/[|\\{}()[\]^$+*?.-]/.test(pattern.character) ? '\\' : '') +
140-
pattern.character +
141-
(after || ''),
142-
'g'
143-
)
144-
}
145-
146130
function numerical(a, b) {
147131
return a - b
148132
}

test.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1363,6 +1363,36 @@ test('Code text', function (t) {
13631363
'should pad w/ a space if the value ends w/ a space'
13641364
)
13651365

1366+
t.equal(
1367+
to({type: 'inlineCode', value: 'a\n-'}),
1368+
'`a -`\n',
1369+
'should prevent breaking out of code (-)'
1370+
)
1371+
1372+
t.equal(
1373+
to({type: 'inlineCode', value: 'a\n#'}),
1374+
'`a #`\n',
1375+
'should prevent breaking out of code (#)'
1376+
)
1377+
1378+
t.equal(
1379+
to({type: 'inlineCode', value: 'a\n1. '}),
1380+
'` a 1. `\n',
1381+
'should prevent breaking out of code (\\d\\.)'
1382+
)
1383+
1384+
t.equal(
1385+
to({type: 'inlineCode', value: 'a\r-'}),
1386+
'`a -`\n',
1387+
'should prevent breaking out of code (cr)'
1388+
)
1389+
1390+
t.equal(
1391+
to({type: 'inlineCode', value: 'a\r\n-'}),
1392+
'`a -`\n',
1393+
'should prevent breaking out of code (crlf)'
1394+
)
1395+
13661396
t.end()
13671397
})
13681398

0 commit comments

Comments
 (0)