mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import sanitizeHtml from 'sanitize-html'
|
import sanitizeHtml from 'sanitize-html'
|
||||||
import { escapeHtmlCharacters } from './utils'
|
import { escapeHtmlCharacters } from './utils'
|
||||||
|
import url from 'url'
|
||||||
|
|
||||||
module.exports = function sanitizePlugin (md, options) {
|
module.exports = function sanitizePlugin (md, options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
@@ -25,7 +26,7 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
const inlineTokens = state.tokens[tokenIdx].children
|
const inlineTokens = state.tokens[tokenIdx].children
|
||||||
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
||||||
if (inlineTokens[childIdx].type === 'html_inline') {
|
if (inlineTokens[childIdx].type === 'html_inline') {
|
||||||
inlineTokens[childIdx].content = sanitizeHtml(
|
inlineTokens[childIdx].content = sanitizeInline(
|
||||||
inlineTokens[childIdx].content,
|
inlineTokens[childIdx].content,
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
@@ -35,3 +36,89 @@ module.exports = function sanitizePlugin (md, options) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
|
||||||
|
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
|
||||||
|
|
||||||
|
function sanitizeInline (html, options) {
|
||||||
|
let match = tagRegex.exec(html)
|
||||||
|
if (!match) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
|
||||||
|
|
||||||
|
if (match[1] !== undefined) {
|
||||||
|
// opening tag
|
||||||
|
const tag = match[1].toLowerCase()
|
||||||
|
if (allowedTags.indexOf(tag) === -1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = match[2]
|
||||||
|
|
||||||
|
let attrs = ''
|
||||||
|
let name
|
||||||
|
let value
|
||||||
|
|
||||||
|
while ((match = attributesRegex.exec(attributes))) {
|
||||||
|
name = match[1].toLowerCase()
|
||||||
|
value = match[3]
|
||||||
|
|
||||||
|
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
|
||||||
|
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
|
||||||
|
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs += ` ${name}`
|
||||||
|
if (match[2]) {
|
||||||
|
attrs += `="${value}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selfClosing.indexOf(tag) === -1) {
|
||||||
|
return '<' + tag + attrs + '>'
|
||||||
|
} else {
|
||||||
|
return '<' + tag + attrs + ' />'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// closing tag
|
||||||
|
if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) {
|
||||||
|
return html
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function naughtyHRef (href, options) {
|
||||||
|
// href = href.replace(/[\x00-\x20]+/g, '')
|
||||||
|
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
|
||||||
|
|
||||||
|
const matches = href.match(/^([a-zA-Z]+)\:/)
|
||||||
|
if (!matches) {
|
||||||
|
if (href.match(/^[\/\\]{2}/)) {
|
||||||
|
return !options.allowProtocolRelative
|
||||||
|
}
|
||||||
|
|
||||||
|
// No scheme
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheme = matches[1].toLowerCase()
|
||||||
|
|
||||||
|
return options.allowedSchemes.indexOf(scheme) === -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function naughtyIFrame (src, options) {
|
||||||
|
try {
|
||||||
|
const parsed = url.parse(src, false, true)
|
||||||
|
|
||||||
|
return options.allowedIframeHostnames.index(parsed.hostname) === -1
|
||||||
|
} catch (e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -106,7 +106,11 @@ class Markdown {
|
|||||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||||
'input': ['type', 'id', 'checked']
|
'input': ['type', 'id', 'checked']
|
||||||
},
|
},
|
||||||
allowedIframeHostnames: ['www.youtube.com']
|
allowedIframeHostnames: ['www.youtube.com'],
|
||||||
|
selfClosing: [ 'img', 'br', 'hr', 'input' ],
|
||||||
|
allowedSchemes: [ 'http', 'https', 'ftp', 'mailto' ],
|
||||||
|
allowedSchemesAppliedToAttributes: [ 'href', 'src', 'cite' ],
|
||||||
|
allowProtocolRelative: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
tests/fixtures/markdowns.js
vendored
5
tests/fixtures/markdowns.js
vendored
@@ -50,11 +50,14 @@ const smartQuotes = 'This is a "QUOTE".'
|
|||||||
|
|
||||||
const breaks = 'This is the first line.\nThis is the second line.'
|
const breaks = 'This is the first line.\nThis is the second line.'
|
||||||
|
|
||||||
|
const shortcuts = '<kbd>Ctrl</kbd>\n\n[[Ctrl]]'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
basic,
|
basic,
|
||||||
codeblock,
|
codeblock,
|
||||||
katex,
|
katex,
|
||||||
checkboxes,
|
checkboxes,
|
||||||
smartQuotes,
|
smartQuotes,
|
||||||
breaks
|
breaks,
|
||||||
|
shortcuts
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,3 +43,8 @@ test('Markdown.render() should render line breaks correctly', t => {
|
|||||||
const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
|
const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
|
||||||
t.snapshot(renderedNonBreaks)
|
t.snapshot(renderedNonBreaks)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Markdown.render() should render shortcuts correctly', t => {
|
||||||
|
const rendered = md.render(markdownFixtures.shortcuts)
|
||||||
|
t.snapshot(rendered)
|
||||||
|
})
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ Generated by [AVA](https://ava.li).
|
|||||||
This is the second line.</p>␊
|
This is the second line.</p>␊
|
||||||
`
|
`
|
||||||
|
|
||||||
|
## Markdown.render() should render shortcuts correctly
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
`<p data-line="0"><kbd>Ctrl</kbd></p>␊
|
||||||
|
<p data-line="2"><kbd>Ctrl</kbd></p>␊
|
||||||
|
`
|
||||||
|
|
||||||
## Markdown.render() should renders KaTeX correctly
|
## Markdown.render() should renders KaTeX correctly
|
||||||
|
|
||||||
> Snapshot 1
|
> Snapshot 1
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user