Skip to content

Commit

Permalink
Fix josdejong#1029: XSS vulnerabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
josdejong committed Jul 1, 2020
1 parent 8826c6f commit 87bc7b2
Show file tree
Hide file tree
Showing 13 changed files with 100 additions and 70 deletions.
1 change: 1 addition & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ https://github.com/josdejong/jsoneditor

## not yet published, version 9.0.2

- Fix #1029: XSS vulnerabilities.
- Fix #1017: unable to style the color of a value containing a color.
Thanks @p3x-robot.

Expand Down
16 changes: 11 additions & 5 deletions src/js/ContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ export class ContextMenu {
buttonExpand.type = 'button'
domItem.buttonExpand = buttonExpand
buttonExpand.className = 'jsoneditor-expand'
buttonExpand.innerHTML = '<div class="jsoneditor-expand"></div>'
const buttonExpandInner = document.createElement('div')
buttonExpandInner.className = 'jsoneditor-expand'
buttonExpand.appendChild(buttonExpandInner)
li.appendChild(buttonExpand)
if (item.submenuTitle) {
buttonExpand.title = item.submenuTitle
Expand Down Expand Up @@ -141,8 +143,14 @@ export class ContextMenu {
createMenuItems(ul, domSubItems, item.submenu)
} else {
// no submenu, just a button with clickhandler
button.innerHTML = '<div class="jsoneditor-icon"></div>' +
'<div class="jsoneditor-text">' + translate(item.text) + '</div>'
const icon = document.createElement('div')
icon.className = 'jsoneditor-icon'
button.appendChild(icon)

const text = document.createElement('div')
text.className = 'jsoneditor-text'
text.appendChild(document.createTextNode(translate(item.text)))
button.appendChild(text)
}

domItems.push(domItem)
Expand Down Expand Up @@ -425,5 +433,3 @@ export class ContextMenu {

// currently displayed context menu, a singleton. We may only have one visible context menu
ContextMenu.visibleMenu = undefined

export default ContextMenu
51 changes: 38 additions & 13 deletions src/js/ErrorTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class ErrorTable {
const additionalErrorsIndication = document.createElement('div')
additionalErrorsIndication.style.display = 'none'
additionalErrorsIndication.className = 'jsoneditor-additional-errors fadein'
additionalErrorsIndication.innerHTML = 'Scroll for more &#9663;'
additionalErrorsIndication.textContent = 'Scroll for more \u25BF'
this.dom.additionalErrorsIndication = additionalErrorsIndication
validationErrorsContainer.appendChild(additionalErrorsIndication)

Expand Down Expand Up @@ -76,19 +76,15 @@ export class ErrorTable {
if (this.errorTableVisible && errors.length > 0) {
const validationErrors = document.createElement('div')
validationErrors.className = 'jsoneditor-validation-errors'
validationErrors.innerHTML = '<table class="jsoneditor-text-errors"><tbody></tbody></table>'
const tbody = validationErrors.getElementsByTagName('tbody')[0]

errors.forEach(error => {
let message
if (typeof error === 'string') {
message = '<td colspan="2"><pre>' + error + '</pre></td>'
} else {
message =
'<td>' + (error.dataPath || '') + '</td>' +
'<td><pre>' + error.message + '</pre></td>'
}
const table = document.createElement('table')
table.className = 'jsoneditor-text-errors'
validationErrors.appendChild(table)

const tbody = document.createElement('tbody')
table.appendChild(tbody)

errors.forEach(error => {
let line

if (!isNaN(error.line)) {
Expand All @@ -108,7 +104,36 @@ export class ErrorTable {
trEl.className += ' validation-error'
}

trEl.innerHTML = ('<td><button class="jsoneditor-schema-error"></button></td><td style="white-space:nowrap;">' + (!isNaN(line) ? ('Ln ' + line) : '') + '</td>' + message)
const td1 = document.createElement('td')
const button = document.createElement('button')
button.className = 'jsoneditor-schema-error'
td1.appendChild(button)
trEl.appendChild(td1)

const td2 = document.createElement('td')
td2.style = 'white-space: nowrap;'
td2.textContent = (!isNaN(line) ? ('Ln ' + line) : '')
trEl.appendChild(td2)

if (typeof error === 'string') {
const td34 = document.createElement('td')
td34.colSpan = 2
const pre = document.createElement('pre')
pre.appendChild(document.createTextNode(error))
td34.appendChild(pre)
trEl.appendChild(td34)
} else {
const td3 = document.createElement('td')
td3.appendChild(document.createTextNode(error.dataPath || ''))
trEl.appendChild(td3)

const td4 = document.createElement('td')
const pre = document.createElement('pre')
pre.appendChild(document.createTextNode(error.message))
td4.appendChild(pre)
trEl.appendChild(td4)
}

trEl.onclick = () => {
this.onFocusLine(line)
}
Expand Down
2 changes: 1 addition & 1 deletion src/js/ModeSwitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class ModeSwitcher {
const box = document.createElement('button')
box.type = 'button'
box.className = 'jsoneditor-modes jsoneditor-separator'
box.innerHTML = currentTitle + ' &#x25BE;'
box.textContent = currentTitle + ' \u25BE'
box.title = translate('modeEditorTitle')
box.onclick = () => {
const menu = new ContextMenu(items)
Expand Down
26 changes: 13 additions & 13 deletions src/js/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -1511,7 +1511,7 @@ export class Node {
if (this.valueInnerText === '' && this.dom.value.innerHTML !== '') {
// When clearing the contents, often a <br/> remains, messing up the
// styling of the empty text box. Therefore we remove the <br/>
this.dom.value.innerHTML = ''
this.dom.value.textContent = ''
}
}

Expand Down Expand Up @@ -1718,14 +1718,14 @@ export class Node {
// Create the default empty option
this.dom.select.option = document.createElement('option')
this.dom.select.option.value = ''
this.dom.select.option.innerHTML = '--'
this.dom.select.option.textContent = '--'
this.dom.select.appendChild(this.dom.select.option)

// Iterate all enum values and add them as options
for (let i = 0; i < this.enum.length; i++) {
this.dom.select.option = document.createElement('option')
this.dom.select.option.value = this.enum[i]
this.dom.select.option.innerHTML = this.enum[i]
this.dom.select.option.textContent = this.enum[i]
if (this.dom.select.option.value === this.value) {
this.dom.select.option.selected = true
}
Expand All @@ -1747,7 +1747,7 @@ export class Node {
) {
this.valueFieldHTML = this.dom.tdValue.innerHTML
this.dom.tdValue.style.visibility = 'hidden'
this.dom.tdValue.innerHTML = ''
this.dom.tdValue.textContent = ''
} else {
delete this.valueFieldHTML
}
Expand Down Expand Up @@ -1804,7 +1804,7 @@ export class Node {
})
}
if (!title) {
this.dom.date.innerHTML = new Date(value).toISOString()
this.dom.date.textContent = new Date(value).toISOString()
} else {
while (this.dom.date.firstChild) {
this.dom.date.removeChild(this.dom.date.firstChild)
Expand Down Expand Up @@ -1892,7 +1892,7 @@ export class Node {
if (this.fieldInnerText === '' && this.dom.field.innerHTML !== '') {
// When clearing the contents, often a <br/> remains, messing up the
// styling of the empty text box. Therefore we remove the <br/>
this.dom.field.innerHTML = ''
this.dom.field.textContent = ''
}
}

Expand Down Expand Up @@ -2349,7 +2349,7 @@ export class Node {
child.index = index
const childField = child.dom.field
if (childField) {
childField.innerHTML = index
childField.textContent = index
}
})
} else if (this.type === 'object') {
Expand All @@ -2375,10 +2375,10 @@ export class Node {

if (this.type === 'array') {
domValue = document.createElement('div')
domValue.innerHTML = '[...]'
domValue.textContent = '[...]'
} else if (this.type === 'object') {
domValue = document.createElement('div')
domValue.innerHTML = '{...}'
domValue.textContent = '{...}'
} else {
if (!this.editable.value && isUrl(this.value)) {
// create a link in case of read-only editor and value containing an url
Expand Down Expand Up @@ -2525,14 +2525,14 @@ export class Node {

// swap the value of a boolean when the checkbox displayed left is clicked
if (type === 'change' && target === dom.checkbox) {
this.dom.value.innerHTML = !this.value
this.dom.value.textContent = String(!this.value)
this._getDomValue()
this._updateDomDefault()
}

// update the value of the node based on the selected option
if (type === 'change' && target === dom.select) {
this.dom.value.innerHTML = dom.select.value
this.dom.value.innerHTML = this._escapeHTML(dom.select.value)
this._getDomValue()
this._updateDomValue()
}
Expand Down Expand Up @@ -4047,7 +4047,7 @@ export class Node {
}
}

this.dom.value.innerHTML = (this.type === 'object')
this.dom.value.textContent = (this.type === 'object')
? ('{' + (nodeName || count) + '}')
: ('[' + (nodeName || count) + ']')
}
Expand Down Expand Up @@ -4555,7 +4555,7 @@ Node.onDuplicate = nodes => {
if (clones[0].parent.type === 'object') {
// when duplicating a single object property,
// set focus to the field and keep the original field name
clones[0].dom.field.innerHTML = nodes[0].field
clones[0].dom.field.innerHTML = this._escapeHTML(nodes[0].field)
clones[0].focus('field')
} else {
clones[0].focus()
Expand Down
10 changes: 5 additions & 5 deletions src/js/SearchBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,16 @@ export class SearchBox {
if (text !== undefined) {
const resultCount = this.results.length
if (resultCount === 0) {
this.dom.results.innerHTML = 'no&nbsp;results'
this.dom.results.textContent = 'no\u00A0results'
} else if (resultCount === 1) {
this.dom.results.innerHTML = '1&nbsp;result'
this.dom.results.textContent = '1\u00A0result'
} else if (resultCount > MAX_SEARCH_RESULTS) {
this.dom.results.innerHTML = MAX_SEARCH_RESULTS + '+&nbsp;results'
this.dom.results.textContent = MAX_SEARCH_RESULTS + '+\u00A0results'
} else {
this.dom.results.innerHTML = resultCount + '&nbsp;results'
this.dom.results.textContent = resultCount + '\u00A0results'
}
} else {
this.dom.results.innerHTML = ''
this.dom.results.textContent = ''
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/js/TreePath.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class TreePath {
* Reset component to initial status
*/
reset () {
this.path.innerHTML = translate('selectNode')
this.path.textContent = translate('selectNode')
}

/**
Expand All @@ -38,7 +38,7 @@ export class TreePath {
setPath (pathObjs) {
const me = this

this.path.innerHTML = ''
this.path.textContent = ''

if (pathObjs && pathObjs.length) {
pathObjs.forEach((pathObj, idx) => {
Expand All @@ -53,7 +53,7 @@ export class TreePath {
if (pathObj.children.length) {
sepEl = document.createElement('span')
sepEl.className = 'jsoneditor-treepath-seperator'
sepEl.innerHTML = '&#9658;'
sepEl.textContent = '\u25BA'

sepEl.onclick = () => {
me.contentMenuClicked = true
Expand Down Expand Up @@ -82,7 +82,7 @@ export class TreePath {
const showAllBtn = document.createElement('span')
showAllBtn.className = 'jsoneditor-treepath-show-all-btn'
showAllBtn.title = 'show all path'
showAllBtn.innerHTML = '...'
showAllBtn.textContent = '...'
showAllBtn.onclick = _onShowAllClick.bind(me, pathObjs)
me.path.insertBefore(showAllBtn, me.path.firstChild)
}
Expand Down
4 changes: 2 additions & 2 deletions src/js/appendNodeFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function appendNodeFactory (Node) {
// a cell for the contents (showing text 'empty')
const tdAppend = document.createElement('td')
const domText = document.createElement('div')
domText.innerHTML = '(' + translate('empty') + ')'
domText.appendChild(document.createTextNode('(' + translate('empty') + ')'))
domText.className = 'jsoneditor-readonly'
tdAppend.appendChild(domText)
dom.td = tdAppend
Expand Down Expand Up @@ -100,7 +100,7 @@ export function appendNodeFactory (Node) {

const domText = dom.text
if (domText) {
domText.innerHTML = '(' + translate('empty') + ' ' + this.parent.type + ')'
domText.firstChild.nodeValue = '(' + translate('empty') + ' ' + this.parent.type + ')'
}

// attach or detach the contents of the append node:
Expand Down
Loading

0 comments on commit 87bc7b2

Please sign in to comment.