Bug fix for issue #724; XSS issue when importing through getHTML() function; remove usage of innerHTML and pre-parse the string using native JS DOMParser

This commit is contained in:
John Nguyen 2020-07-01 14:01:31 -07:00
parent 4954f8297c
commit 5d17f68c0c

View File

@ -4,18 +4,18 @@ import {
PluginKey, PluginKey,
TextSelection, TextSelection,
} from 'prosemirror-state' } from 'prosemirror-state'
import {EditorView} from 'prosemirror-view' import { EditorView } from 'prosemirror-view'
import {Schema, DOMParser, DOMSerializer} from 'prosemirror-model' import { Schema, DOMParser, DOMSerializer } from 'prosemirror-model'
import {dropCursor} from 'prosemirror-dropcursor' import { dropCursor } from 'prosemirror-dropcursor'
import {gapCursor} from 'prosemirror-gapcursor' import { gapCursor } from 'prosemirror-gapcursor'
import {keymap} from 'prosemirror-keymap' import { keymap } from 'prosemirror-keymap'
import {baseKeymap} from 'prosemirror-commands' import { baseKeymap } from 'prosemirror-commands'
import {inputRules, undoInputRule} from 'prosemirror-inputrules' import { inputRules, undoInputRule } from 'prosemirror-inputrules'
import { import {
markIsActive, markIsActive,
nodeIsActive, nodeIsActive,
getMarkAttrs, getMarkAttrs,
getNodeAttrs, getNodeAttrs,
} from 'tiptap-utils' } from 'tiptap-utils'
import { import {
injectCSS, injectCSS,
@ -25,7 +25,7 @@ import {
ComponentView, ComponentView,
minMax, minMax,
} from './Utils' } from './Utils'
import {Doc, Paragraph, Text} from './Nodes' import { Doc, Paragraph, Text } from './Nodes'
import css from './style.css' import css from './style.css'
export default class Editor extends Emitter { export default class Editor extends Emitter {
@ -52,20 +52,13 @@ export default class Editor extends Emitter {
dropCursor: {}, dropCursor: {},
parseOptions: {}, parseOptions: {},
injectCSS: true, injectCSS: true,
onInit: () => { onInit: () => {},
}, onTransaction: () => {},
onTransaction: () => { onUpdate: () => {},
}, onFocus: () => {},
onUpdate: () => { onBlur: () => {},
}, onPaste: () => {},
onFocus: () => { onDrop: () => {},
},
onBlur: () => {
},
onPaste: () => {
},
onDrop: () => {
},
} }
this.events = [ this.events = [
@ -87,7 +80,7 @@ export default class Editor extends Emitter {
...options, ...options,
}) })
this.focused = false this.focused = false
this.selection = {from: 0, to: 0} this.selection = { from: 0, to: 0 }
this.element = document.createElement('div') this.element = document.createElement('div')
this.extensions = this.createExtensions() this.extensions = this.createExtensions()
this.nodes = this.createNodes() this.nodes = this.createNodes()
@ -110,8 +103,7 @@ export default class Editor extends Emitter {
} }
this.events.forEach(name => { this.events.forEach(name => {
this.on(name, this.options[camelCase(`on ${name}`)] || (() => { this.on(name, this.options[camelCase(`on ${name}`)] || (() => {}))
}))
}) })
this.emit('init', { this.emit('init', {
@ -283,7 +275,6 @@ export default class Editor extends Emitter {
const htmlString = `<div>${content}</div>`; const htmlString = `<div>${content}</div>`;
const parser = new window.DOMParser; const parser = new window.DOMParser;
const element = parser.parseFromString(htmlString, "text/html").body.firstChild; const element = parser.parseFromString(htmlString, "text/html").body.firstChild;
return DOMParser.fromSchema(this.schema).parse(element, parseOptions) return DOMParser.fromSchema(this.schema).parse(element, parseOptions)
} }
@ -293,12 +284,8 @@ export default class Editor extends Emitter {
createView() { createView() {
return new EditorView(this.element, { return new EditorView(this.element, {
state: this.createState(), state: this.createState(),
handlePaste: (...args) => { handlePaste: (...args) => { this.emit('paste', ...args) },
this.emit('paste', ...args) handleDrop: (...args) => { this.emit('drop', ...args) },
},
handleDrop: (...args) => {
this.emit('drop', ...args)
},
dispatchTransaction: this.dispatchTransaction.bind(this), dispatchTransaction: this.dispatchTransaction.bind(this),
}) })
} }
@ -319,7 +306,7 @@ export default class Editor extends Emitter {
}) })
} }
initNodeViews({parent, extensions}) { initNodeViews({ parent, extensions }) {
return extensions return extensions
.filter(extension => ['node', 'mark'].includes(extension.type)) .filter(extension => ['node', 'mark'].includes(extension.type))
.filter(extension => extension.view) .filter(extension => extension.view)
@ -390,7 +377,7 @@ export default class Editor extends Emitter {
} }
if (position === 'end') { if (position === 'end') {
const {doc} = this.state const { doc } = this.state
return { return {
from: doc.content.size, from: doc.content.size,
to: doc.content.size, to: doc.content.size,
@ -408,14 +395,14 @@ export default class Editor extends Emitter {
return return
} }
const {from, to} = this.resolveSelection(position) const { from, to } = this.resolveSelection(position)
this.setSelection(from, to) this.setSelection(from, to)
setTimeout(() => this.view.focus(), 10) setTimeout(() => this.view.focus(), 10)
} }
setSelection(from = 0, to = 0) { setSelection(from = 0, to = 0) {
const {doc, tr} = this.state const { doc, tr } = this.state
const resolvedFrom = minMax(from, 0, doc.content.size) const resolvedFrom = minMax(from, 0, doc.content.size)
const resolvedEnd = minMax(to, 0, doc.content.size) const resolvedEnd = minMax(to, 0, doc.content.size)
const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd) const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd)
@ -451,7 +438,7 @@ export default class Editor extends Emitter {
} }
setContent(content = {}, emitUpdate = false, parseOptions) { setContent(content = {}, emitUpdate = false, parseOptions) {
const {doc, tr} = this.state const { doc, tr } = this.state
const document = this.createDocument(content, parseOptions) const document = this.createDocument(content, parseOptions)
const selection = TextSelection.create(doc, 0, doc.content.size) const selection = TextSelection.create(doc, 0, doc.content.size)
const transaction = tr const transaction = tr
@ -515,7 +502,7 @@ export default class Editor extends Emitter {
const plugins = typeof handlePlugins === 'function' const plugins = typeof handlePlugins === 'function'
? handlePlugins(plugin, this.state.plugins) ? handlePlugins(plugin, this.state.plugins)
: [plugin, ...this.state.plugins] : [plugin, ...this.state.plugins]
const newState = this.state.reconfigure({plugins}) const newState = this.state.reconfigure({ plugins })
this.view.updateState(newState) this.view.updateState(newState)
} }