From dccb93c8a8002dca2ab5e8ecaefc5c8b3628d7c1 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 30 Apr 2024 23:19:19 +0200 Subject: [PATCH] fix: make sure we escape all innerHTML calls --- umap/static/umap/js/umap.controls.js | 76 +++++++++++++++---------- umap/static/umap/js/umap.core.js | 7 ++- umap/static/umap/js/umap.forms.js | 36 ++++++------ umap/static/umap/js/umap.permissions.js | 11 +++- umap/static/umap/js/umap.popup.js | 2 +- umap/static/umap/js/umap.share.js | 2 +- umap/static/umap/js/umap.ui.js | 8 +-- 7 files changed, 85 insertions(+), 57 deletions(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index a77d7ae6..3d6da688 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -669,20 +669,29 @@ const ControlsMixin = { L.DomUtil.createTitle(container, this.options.name, 'icon-caption') this.permissions.addOwnerLink('h5', container) if (this.options.description) { - const description = L.DomUtil.create('div', 'umap-map-description', container) - description.innerHTML = U.Utils.toHTML(this.options.description) + const description = L.DomUtil.element( + 'div', + { + className: 'umap-map-description', + safeHTML: U.Utils.toHTML(this.options.description), + }, + container + ) } const datalayerContainer = L.DomUtil.create('div', 'datalayer-container', container) this.eachVisibleDataLayer((datalayer) => { if (!datalayer.options.inCaption) return const p = L.DomUtil.create('p', 'datalayer-legend', datalayerContainer), legend = L.DomUtil.create('span', '', p), - headline = L.DomUtil.create('strong', '', p), - description = L.DomUtil.create('span', '', p) + headline = L.DomUtil.create('strong', '', p) datalayer.onceLoaded(function () { datalayer.renderLegend(legend) if (datalayer.options.description) { - description.innerHTML = U.Utils.toHTML(datalayer.options.description) + L.DomUtil.element( + 'span', + { safeHTML: U.Utils.toHTML(datalayer.options.description) }, + p + ) } }) datalayer.renderToolbox(headline) @@ -692,11 +701,12 @@ const ControlsMixin = { credits = L.DomUtil.createFieldset(creditsContainer, L._('Credits')) title = L.DomUtil.add('h5', '', credits, L._('User content credits')) if (this.options.shortCredit || this.options.longCredit) { - L.DomUtil.add( + L.DomUtil.element( 'p', - '', - credits, - U.Utils.toHTML(this.options.longCredit || this.options.shortCredit) + { + safeHTML: U.Utils.toHTML(this.options.longCredit || this.options.shortCredit), + }, + credits ) } if (this.options.licence) { @@ -718,21 +728,26 @@ const ControlsMixin = { L.DomUtil.create('hr', '', credits) title = L.DomUtil.create('h5', '', credits) title.textContent = L._('Map background credits') - const tilelayerCredit = L.DomUtil.create('p', '', credits), - name = L.DomUtil.create('strong', '', tilelayerCredit), - attribution = L.DomUtil.create('span', '', tilelayerCredit) - name.textContent = `${this.selected_tilelayer.options.name} ` - attribution.innerHTML = this.selected_tilelayer.getAttribution() + const tilelayerCredit = L.DomUtil.create('p', '', credits) + L.DomUtil.element( + 'strong', + { textContent: `${this.selected_tilelayer.options.name} ` }, + tilelayerCredit + ) + L.DomUtil.element( + 'span', + { safeHTML: this.selected_tilelayer.getAttribution() }, + tilelayerCredit + ) L.DomUtil.create('hr', '', credits) - const umapCredit = L.DomUtil.create('p', '', credits), - urls = { - leaflet: 'http://leafletjs.com', - django: 'https://www.djangoproject.com', - umap: 'http://wiki.openstreetmap.org/wiki/UMap', - changelog: 'https://umap-project.readthedocs.io/en/master/changelog/', - version: this.options.umap_version, - } - umapCredit.innerHTML = L._( + const urls = { + leaflet: 'http://leafletjs.com', + django: 'https://www.djangoproject.com', + umap: 'http://wiki.openstreetmap.org/wiki/UMap', + changelog: 'https://umap-project.readthedocs.io/en/master/changelog/', + version: this.options.umap_version, + } + const creditHTML = L._( ` Powered by Leaflet and Django, @@ -741,6 +756,7 @@ const ControlsMixin = { `, urls ) + L.DomUtil.element('p', { innerHTML: creditHTML }, credits) this.panel.open({ content: container }) }, @@ -1052,16 +1068,16 @@ U.AttributionControl = L.Control.Attribution.extend({ // Use our own container, so we can hide/show on small screens const credits = this._container.innerHTML this._container.innerHTML = '' - const container = L.DomUtil.add( - 'div', - 'attribution-container', - this._container, - credits - ) + const container = L.DomUtil.create('div', 'attribution-container', this._container) + container.innerHTML = credits const shortCredit = this._map.getOption('shortCredit'), captionMenus = this._map.getOption('captionMenus') if (shortCredit) { - L.DomUtil.add('span', '', container, ` — ${U.Utils.toHTML(shortCredit)}`) + L.DomUtil.element( + 'span', + { safeHTML: ` — ${U.Utils.toHTML(shortCredit)}` }, + container + ) } if (captionMenus) { const link = L.DomUtil.add('a', '', container, ` — ${L._('About')}`) diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 06313859..24ebe4a2 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -73,7 +73,7 @@ L.DomUtil.add = (tagName, className, container, content) => { if (content.nodeType && content.nodeType === 1) { el.appendChild(content) } else { - el.innerHTML = content + el.textContent = content } } return el @@ -165,6 +165,11 @@ L.DomUtil.classIf = (el, className, bool) => { L.DomUtil.element = (what, attrs, parent) => { const el = document.createElement(what) + if (attrs.innerHTML) { + attrs.innerHTML = U.Utils.escapeHTML(attrs.innerHTML) + } else if (attrs.safeHTML) { + attrs.innerHTML = attrs.safeHTML + } for (const attr in attrs) { el[attr] = attrs[attr] } diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index 8f9619e2..22ea7d7f 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -757,12 +757,12 @@ L.FormBuilder.FacetSearchChoices = L.FormBuilder.Element.extend({ }, buildLabel: function () { - this.label = L.DomUtil.element('legend', {textContent: this.options.label}) + this.label = L.DomUtil.element('legend', { textContent: this.options.label }) }, buildLi: function (value) { const property_li = L.DomUtil.create('li', '', this.ul) - const label = L.DomUtil.add('label', '', property_li) + const label = L.DomUtil.create('label', '', property_li) const input = L.DomUtil.create('input', '', label) L.DomUtil.add('span', '', label, value) @@ -800,14 +800,14 @@ L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({ build: function () { this.container = L.DomUtil.create('fieldset', 'umap-facet', this.parentNode) this.container.appendChild(this.label) - const {min, max, type} = this.options.criteria + const { min, max, type } = this.options.criteria this.type = type this.inputType = this.getInputType(this.type) const [minLabel, maxLabel] = this.getLabels() this.minLabel = L.DomUtil.create('label', '', this.container) - this.minLabel.innerHTML = minLabel + this.minLabel.textContent = minLabel this.minInput = L.DomUtil.create('input', '', this.minLabel) this.minInput.type = this.inputType @@ -817,9 +817,8 @@ L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({ this.minInput.dataset.value = min } - this.maxLabel = L.DomUtil.create('label', '', this.container) - this.maxLabel.innerHTML = maxLabel + this.maxLabel.textContent = maxLabel this.maxInput = L.DomUtil.create('input', '', this.maxLabel) this.maxInput.type = this.inputType @@ -834,7 +833,7 @@ L.FormBuilder.MinMaxBase = L.FormBuilder.Element.extend({ }, buildLabel: function () { - this.label = L.DomUtil.element('legend', {textContent: this.options.label}) + this.label = L.DomUtil.element('legend', { textContent: this.options.label }) }, toJS: function () { @@ -974,22 +973,25 @@ L.FormBuilder.Range = L.FormBuilder.FloatInput.extend({ }, buildHelpText: function () { - const datalist = L.DomUtil.create( - 'datalist', - 'umap-field-datalist', - this.getHelpTextParent() - ) - datalist.id = `range-${this.options.label || this.name}` - this.input.setAttribute('list', datalist.id) let options = '' - const step = this.options.step || 1, - digits = step < 1 ? 1 : 0 + const step = this.options.step || 1 + const digits = step < 1 ? 1 : 0 + const id = `range-${this.options.label || this.name}` for (let i = this.options.min; i <= this.options.max; i += this.options.step) { options += `` } - datalist.innerHTML = options + const datalist = L.DomUtil.element( + 'datalist', + { + className: 'umap-field-datalist', + safeHTML: options, + id: id, + }, + this.getHelpTextParent() + ) + this.input.setAttribute('list', id) L.FormBuilder.Input.prototype.buildHelpText.call(this) }, }) diff --git a/umap/static/umap/js/umap.permissions.js b/umap/static/umap/js/umap.permissions.js index dcdd2bfd..7b33ff73 100644 --- a/umap/static/umap/js/umap.permissions.js +++ b/umap/static/umap/js/umap.permissions.js @@ -62,9 +62,14 @@ U.MapPermissions = L.Class.extend({ L.DomUtil.createTitle(container, L._('Update permissions'), 'icon-key') if (this.isAnonymousMap()) { if (this.options.anonymous_edit_url) { - const helpText = `${L._('Secret edit link:')}
${this.options.anonymous_edit_url - }` - L.DomUtil.add('p', 'help-text', container, helpText) + const helpText = `${L._('Secret edit link:')}
${ + this.options.anonymous_edit_url + }` + L.DomUtil.element( + 'p', + { className: 'help-text', innerHTML: helpText }, + container + ) fields.push([ 'options.edit_status', { diff --git a/umap/static/umap/js/umap.popup.js b/umap/static/umap/js/umap.popup.js index 2421c954..6e9a96d8 100644 --- a/umap/static/umap/js/umap.popup.js +++ b/umap/static/umap/js/umap.popup.js @@ -190,7 +190,7 @@ U.PopupTemplate.Table = U.PopupTemplate.BaseWithTitle.extend({ addRow: function (container, key, value) { const tr = L.DomUtil.create('tr', '', container) L.DomUtil.add('th', '', tr, key) - L.DomUtil.add('td', '', tr, this.formatRow(key, value)) + L.DomUtil.element('td', { innerHTML: this.formatRow(key, value) }, tr) }, renderBody: function () { diff --git a/umap/static/umap/js/umap.share.js b/umap/static/umap/js/umap.share.js index 1c8650ec..459183ac 100644 --- a/umap/static/umap/js/umap.share.js +++ b/umap/static/umap/js/umap.share.js @@ -143,7 +143,7 @@ U.Share = L.Class.extend({ } const iframeExporter = new U.IframeExporter(this.map) const buildIframeCode = () => { - iframe.innerHTML = iframeExporter.build() + iframe.textContent = iframeExporter.build() exportUrl.value = window.location.protocol + iframeExporter.buildUrl() } buildIframeCode() diff --git a/umap/static/umap/js/umap.ui.js b/umap/static/umap/js/umap.ui.js index 4f238244..f7cb4a4f 100644 --- a/umap/static/umap/js/umap.ui.js +++ b/umap/static/umap/js/umap.ui.js @@ -51,13 +51,13 @@ U.UI = L.Evented.extend({ close, this ) - L.DomUtil.add('i', 'umap-close-icon', closeButton) + L.DomUtil.create('i', 'umap-close-icon', closeButton) const label = L.DomUtil.create('span', '', closeButton) label.title = label.textContent = L._('Close') - L.DomUtil.add('div', '', this._alert, e.content) + L.DomUtil.element('div', {innerHTML: e.content}, this._alert) if (e.actions) { let action, el, input - const form = L.DomUtil.add('div', 'umap-alert-actions', this._alert) + const form = L.DomUtil.create('div', 'umap-alert-actions', this._alert) for (let i = 0; i < e.actions.length; i++) { action = e.actions[i] if (action.input) { @@ -97,7 +97,7 @@ U.UI = L.Evented.extend({ this.anchorTooltipAbsolute() } L.DomUtil.addClass(this.parent, 'umap-tooltip') - this._tooltip.innerHTML = opts.content + this._tooltip.innerHTML = U.Utils.escapeHTML(opts.content) } this.TOOLTIP_ID = window.setTimeout(L.bind(showIt, this), opts.delay || 0) const id = this.TOOLTIP_ID