Skip to content

Commit efa0072

Browse files
authored
Merge pull request #2342 from daiyam/bug-1992
fixing inline html
2 parents 106caf2 + ae9e83c commit efa0072

File tree

6 files changed

+110
-3
lines changed

6 files changed

+110
-3
lines changed

browser/lib/markdown-it-sanitize-html.js

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import sanitizeHtml from 'sanitize-html'
44
import { escapeHtmlCharacters } from './utils'
5+
import url from 'url'
56

67
module.exports = function sanitizePlugin (md, options) {
78
options = options || {}
@@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) {
2526
const inlineTokens = state.tokens[tokenIdx].children
2627
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
2728
if (inlineTokens[childIdx].type === 'html_inline') {
28-
inlineTokens[childIdx].content = sanitizeHtml(
29+
inlineTokens[childIdx].content = sanitizeInline(
2930
inlineTokens[childIdx].content,
3031
options
3132
)
@@ -35,3 +36,89 @@ module.exports = function sanitizePlugin (md, options) {
3536
}
3637
})
3738
}
39+
40+
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
41+
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
42+
43+
function sanitizeInline (html, options) {
44+
let match = tagRegex.exec(html)
45+
if (!match) {
46+
return ''
47+
}
48+
49+
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
50+
51+
if (match[1] !== undefined) {
52+
// opening tag
53+
const tag = match[1].toLowerCase()
54+
if (allowedTags.indexOf(tag) === -1) {
55+
return ''
56+
}
57+
58+
const attributes = match[2]
59+
60+
let attrs = ''
61+
let name
62+
let value
63+
64+
while ((match = attributesRegex.exec(attributes))) {
65+
name = match[1].toLowerCase()
66+
value = match[3]
67+
68+
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
69+
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
70+
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
71+
continue
72+
}
73+
}
74+
75+
attrs += ` ${name}`
76+
if (match[2]) {
77+
attrs += `="${value}"`
78+
}
79+
}
80+
}
81+
82+
if (selfClosing.indexOf(tag) === -1) {
83+
return '<' + tag + attrs + '>'
84+
} else {
85+
return '<' + tag + attrs + ' />'
86+
}
87+
} else {
88+
// closing tag
89+
if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) {
90+
return html
91+
} else {
92+
return ''
93+
}
94+
}
95+
}
96+
97+
function naughtyHRef (href, options) {
98+
// href = href.replace(/[\x00-\x20]+/g, '')
99+
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
100+
101+
const matches = href.match(/^([a-zA-Z]+)\:/)
102+
if (!matches) {
103+
if (href.match(/^[\/\\]{2}/)) {
104+
return !options.allowProtocolRelative
105+
}
106+
107+
// No scheme
108+
return false
109+
}
110+
111+
const scheme = matches[1].toLowerCase()
112+
113+
return options.allowedSchemes.indexOf(scheme) === -1
114+
}
115+
116+
function naughtyIFrame (src, options) {
117+
try {
118+
const parsed = url.parse(src, false, true)
119+
120+
return options.allowedIframeHostnames.index(parsed.hostname) === -1
121+
} catch (e) {
122+
return true
123+
}
124+
}

browser/lib/markdown.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,11 @@ class Markdown {
106106
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
107107
'input': ['type', 'id', 'checked']
108108
},
109-
allowedIframeHostnames: ['www.youtube.com']
109+
allowedIframeHostnames: ['www.youtube.com'],
110+
selfClosing: [ 'img', 'br', 'hr', 'input' ],
111+
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
112+
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
113+
allowProtocolRelative: true
110114
})
111115
}
112116

tests/fixtures/markdowns.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,14 @@ const smartQuotes = 'This is a "QUOTE".'
5050

5151
const breaks = 'This is the first line.\nThis is the second line.'
5252

53+
const shortcuts = '<kbd>Ctrl</kbd>\n\n[[Ctrl]]'
54+
5355
export default {
5456
basic,
5557
codeblock,
5658
katex,
5759
checkboxes,
5860
smartQuotes,
59-
breaks
61+
breaks,
62+
shortcuts
6063
}

tests/lib/markdown-test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,8 @@ test('Markdown.render() should render line breaks correctly', t => {
4343
const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
4444
t.snapshot(renderedNonBreaks)
4545
})
46+
47+
test('Markdown.render() should render shortcuts correctly', t => {
48+
const rendered = md.render(markdownFixtures.shortcuts)
49+
t.snapshot(rendered)
50+
})

tests/lib/snapshots/markdown-test.js.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ Generated by [AVA](https://ava.li).
1818
This is the second line.</p>␊
1919
`
2020

21+
## Markdown.render() should render shortcuts correctly
22+
23+
> Snapshot 1
24+
25+
`<p data-line="0"><kbd>Ctrl</kbd></p>␊
26+
<p data-line="2"><kbd>Ctrl</kbd></p>␊
27+
`
28+
2129
## Markdown.render() should renders KaTeX correctly
2230

2331
> Snapshot 1
47 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)