Skip to content

Commit a2ef519

Browse files
committed
Fix literal backslashes before constructs
Closes GH-19.
1 parent 07054ca commit a2ef519

File tree

3 files changed

+60
-21
lines changed

3 files changed

+60
-21
lines changed

lib/unsafe.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,10 @@ module.exports = [
7777
// Question mark and at sign are not used in markdown for constructs.
7878
// A left bracket can start definitions, references, labels,
7979
{atBreak: true, character: '['},
80-
{
81-
character: '[',
82-
inConstruct: ['phrasing', 'label', 'reference']
83-
},
80+
{character: '[', inConstruct: ['phrasing', 'label', 'reference']},
8481
// A backslash can start an escape (when followed by punctuation) or a
8582
// hard break (when followed by an eol).
86-
{character: '\\', after: '[!-/:-@[-`{-~]', collapse: false},
83+
// Note: typical escapes are handled in `safe`!
8784
{character: '\\', after: '[\\r\\n]', inConstruct: 'phrasing'},
8885
// A right bracket can exit labels.
8986
{

lib/util/safe.js

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,8 @@ function safe(context, input, config) {
2828
expression = patternCompile(pattern)
2929

3030
while ((match = expression.exec(value))) {
31-
// Often, patterns which depend on a character before or after it, such
32-
// as `!` when followed by `[` (for an image), do not have to be escaped
33-
// when the other character has to be escaped as well.
34-
// But in the case of a backslash, such as `\` when followed by `*`, that
35-
// is not correct: both have to be escaped.
36-
// This is a naïve “fix” for that though, but this seems the simplest for
37-
// now.
38-
if (pattern.collapse === false) {
39-
before = false
40-
after = false
41-
} else {
42-
before = 'before' in pattern || pattern.atBreak
43-
after = 'after' in pattern
44-
}
31+
before = 'before' in pattern || pattern.atBreak
32+
after = 'after' in pattern
4533

4634
position = match.index + (before ? match[1].length : 0)
4735

@@ -91,7 +79,10 @@ function safe(context, input, config) {
9179
}
9280

9381
if (start !== position) {
94-
result.push(value.slice(start, position))
82+
// If we have to use a character reference, an ampersand would be more
83+
// correct, but as backslashes only care about punctuation, either will
84+
// do the trick
85+
result.push(escapeBackslashes(value.slice(start, position), '\\'))
9586
}
9687

9788
start = position
@@ -111,11 +102,38 @@ function safe(context, input, config) {
111102
}
112103
}
113104

114-
result.push(value.slice(start, end))
105+
result.push(escapeBackslashes(value.slice(start, end), config.after))
115106

116107
return result.join('')
117108
}
118109

119110
function numerical(a, b) {
120111
return a - b
121112
}
113+
114+
function escapeBackslashes(value, after) {
115+
var expression = /\\(?=[!-/:-@[-`{-~])/g
116+
var positions = []
117+
var results = []
118+
var index = -1
119+
var start = 0
120+
var whole = value + after
121+
var match
122+
123+
while ((match = expression.exec(whole))) {
124+
positions.push(match.index)
125+
}
126+
127+
while (++index < positions.length) {
128+
if (start !== positions[index]) {
129+
results.push(value.slice(start, positions[index]))
130+
}
131+
132+
results.push('\\')
133+
start = positions[index]
134+
}
135+
136+
results.push(value.slice(start))
137+
138+
return results.join('')
139+
}

test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2714,6 +2714,30 @@ test('escape', function (t) {
27142714
'should prefer extension options over subextension options'
27152715
)
27162716

2717+
t.equal(
2718+
to({
2719+
type: 'paragraph',
2720+
children: [
2721+
{type: 'text', value: '\\'},
2722+
{type: 'emphasis', children: [{type: 'text', value: 'a'}]}
2723+
]
2724+
}),
2725+
'\\\\*a*\n',
2726+
'should handle literal backslashes properly when before constructs (1)'
2727+
)
2728+
2729+
t.equal(
2730+
to({
2731+
type: 'paragraph',
2732+
children: [
2733+
{type: 'text', value: '\\\\'},
2734+
{type: 'emphasis', children: [{type: 'text', value: 'a'}]}
2735+
]
2736+
}),
2737+
'\\\\\\\\*a*\n',
2738+
'should handle literal backslashes properly when before constructs (2)'
2739+
)
2740+
27172741
t.end()
27182742
})
27192743

0 commit comments

Comments
 (0)