diff --git a/js/src/autocomplete.js b/js/src/autocomplete.js
index 3a7af7b2e..413412c15 100644
--- a/js/src/autocomplete.js
+++ b/js/src/autocomplete.js
@@ -926,7 +926,7 @@ class Autocomplete extends BaseComponent {
if (this._config.searchNoResultsLabel) {
const placeholder = document.createElement('div')
placeholder.classList.add(CLASS_NAME_OPTIONS_EMPTY)
- placeholder.innerHTML = this._config.searchNoResultsLabel
+ placeholder.textContent = this._config.searchNoResultsLabel
if (!SelectorEngine.findOne(SELECTOR_OPTIONS_EMPTY, this._menu)) {
SelectorEngine.findOne(SELECTOR_OPTIONS, this._menu).append(placeholder)
diff --git a/js/src/calendar.js b/js/src/calendar.js
index 3c2ee0bb7..36dfec862 100644
--- a/js/src/calendar.js
+++ b/js/src/calendar.js
@@ -10,7 +10,7 @@ import BaseComponent from './base-component.js'
import EventHandler from './dom/event-handler.js'
import Manipulator from './dom/manipulator.js'
import SelectorEngine from './dom/selector-engine.js'
-import { DefaultAllowlist, sanitizeHtml } from './util/sanitizer.js'
+import { DefaultAllowlist, escapeHtml, sanitizeHtml } from './util/sanitizer.js'
import { defineJQueryPlugin } from './util/index.js'
import {
convertToDateObject,
@@ -606,10 +606,10 @@ class Calendar extends BaseComponent {
navigationElement.classList.add('calendar-nav')
navigationElement.innerHTML = `
-
@@ -622,10 +622,10 @@ class Calendar extends BaseComponent {
- ${this._view === 'days' ? `
+ ${this._view === 'days' ? `
` : ''}
-
+
@@ -644,7 +644,7 @@ class Calendar extends BaseComponent {
${this._config.showWeekNumber ?
`
| ` : ''
}
diff --git a/js/src/date-range-picker.js b/js/src/date-range-picker.js
index 97e90379a..c7f6fbbd9 100644
--- a/js/src/date-range-picker.js
+++ b/js/src/date-range-picker.js
@@ -779,7 +779,7 @@ class DateRangePicker extends BaseComponent {
this._calendar.update(this._getCalendarConfig())
})
- buttonEl.innerHTML = key
+ buttonEl.textContent = key
dateRangePickerRangesEl.append(buttonEl)
}
@@ -881,7 +881,7 @@ class DateRangePicker extends BaseComponent {
const todayButtonEl = document.createElement('button')
todayButtonEl.classList.add(...this._getButtonClasses(this._config.todayButtonClasses))
todayButtonEl.type = 'button'
- todayButtonEl.innerHTML = this._config.todayButton
+ todayButtonEl.textContent = this._config.todayButton
todayButtonEl.addEventListener('click', () => {
const date = new Date()
this._calendarDate = date
@@ -901,7 +901,7 @@ class DateRangePicker extends BaseComponent {
const cancelButtonEl = document.createElement('button')
cancelButtonEl.classList.add(...this._getButtonClasses(this._config.cancelButtonClasses))
cancelButtonEl.type = 'button'
- cancelButtonEl.innerHTML = this._config.cancelButton
+ cancelButtonEl.textContent = this._config.cancelButton
cancelButtonEl.addEventListener('click', () => {
this.cancel()
})
@@ -913,7 +913,7 @@ class DateRangePicker extends BaseComponent {
const confirmButtonEl = document.createElement('button')
confirmButtonEl.classList.add(...this._getButtonClasses(this._config.confirmButtonClasses))
confirmButtonEl.type = 'button'
- confirmButtonEl.innerHTML = this._config.confirmButton
+ confirmButtonEl.textContent = this._config.confirmButton
confirmButtonEl.addEventListener('click', () => {
this.hide()
})
diff --git a/js/src/range-slider.js b/js/src/range-slider.js
index e80279b21..f4847ec38 100644
--- a/js/src/range-slider.js
+++ b/js/src/range-slider.js
@@ -406,7 +406,9 @@ class RangeSlider extends BaseComponent {
}
if (this._tooltips[index]) {
- this._tooltips[index].children[0].innerHTML = this._config.tooltipsFormat ? this._config.tooltipsFormat(value) : value
+ this._tooltips[index].children[0].innerHTML = this._config.tooltipsFormat ?
+ (this._config.sanitize ? sanitizeHtml(this._config.tooltipsFormat(value), this._config.allowList, this._config.sanitizeFn) : this._config.tooltipsFormat(value)) :
+ value
const input = SelectorEngine.find(SELECTOR_RANGE_SLIDER_INPUT, this._element)[index]
this._positionTooltip(this._tooltips[index], input)
}
diff --git a/js/src/time-picker.js b/js/src/time-picker.js
index aecd98d5a..403bbf311 100644
--- a/js/src/time-picker.js
+++ b/js/src/time-picker.js
@@ -773,7 +773,7 @@ class TimePicker extends BaseComponent {
...this._getButtonClasses(this._config.cancelButtonClasses)
)
cancelButtonEl.type = 'button'
- cancelButtonEl.innerHTML = this._config.cancelButton
+ cancelButtonEl.textContent = this._config.cancelButton
cancelButtonEl.addEventListener('click', () => {
this.cancel()
})
@@ -787,7 +787,7 @@ class TimePicker extends BaseComponent {
...this._getButtonClasses(this._config.confirmButtonClasses)
)
confirmButtonEl.type = 'button'
- confirmButtonEl.innerHTML = this._config.confirmButton
+ confirmButtonEl.textContent = this._config.confirmButton
confirmButtonEl.addEventListener('click', () => {
this.hide()
})
diff --git a/js/tests/unit/autocomplete.spec.js b/js/tests/unit/autocomplete.spec.js
index e90490b9c..af9e44a28 100644
--- a/js/tests/unit/autocomplete.spec.js
+++ b/js/tests/unit/autocomplete.spec.js
@@ -479,6 +479,23 @@ describe('Autocomplete', () => {
expect(autocomplete._isShown()).toBe(true)
})
+ it('should render searchNoResultsLabel as text, not markup', () => {
+ fixtureEl.innerHTML = ''
+ const autocompleteEl = fixtureEl.querySelector('.autocomplete')
+ const autocomplete = new Autocomplete(autocompleteEl, {
+ searchNoResultsLabel: '
',
+ options: [{ label: 'Option 1', value: '1' }]
+ })
+
+ autocomplete.show()
+ autocomplete._search = 'nonexistent'
+ autocomplete._filterOptionsList()
+
+ const placeholder = autocomplete._menu.querySelector('.autocomplete-options-empty')
+ expect(placeholder.querySelector('img')).toBeNull()
+ expect(placeholder.textContent).toBe('
')
+ })
+
it('should show with container mode', () => {
fixtureEl.innerHTML = ''
const autocompleteEl = fixtureEl.querySelector('.autocomplete')
diff --git a/js/tests/unit/calendar.spec.js b/js/tests/unit/calendar.spec.js
index 4adb7ccad..5e531e864 100644
--- a/js/tests/unit/calendar.spec.js
+++ b/js/tests/unit/calendar.spec.js
@@ -2664,5 +2664,30 @@ describe('Calendar', () => {
expect(btnDoubleNext.getAttribute('aria-label')).toEqual('Go to next year')
expect(btnDoublePrev.getAttribute('aria-label')).toEqual('Go to previous year')
})
+
+ it('should escape aria labels so they cannot break out of the attribute', () => {
+ fixtureEl.innerHTML = ''
+
+ const div = fixtureEl.querySelector('div')
+ new Calendar(div, { // eslint-disable-line no-new
+ ariaNavNextYearLabel: '">
'
+ })
+
+ expect(div.querySelector('.calendar-nav img')).toBeNull()
+ expect(div.querySelector('.btn-double-next').getAttribute('aria-label'))
+ .toEqual('">
')
+ })
+
+ it('should escape the week numbers label', () => {
+ fixtureEl.innerHTML = ''
+
+ const div = fixtureEl.querySelector('div')
+ new Calendar(div, { // eslint-disable-line no-new
+ showWeekNumber: true,
+ weekNumbersLabel: '
'
+ })
+
+ expect(div.querySelector('.calendar-header-cell-inner img')).toBeNull()
+ })
})
})
diff --git a/js/tests/unit/range-slider.spec.js b/js/tests/unit/range-slider.spec.js
index 9a7904743..80e0a45c7 100644
--- a/js/tests/unit/range-slider.spec.js
+++ b/js/tests/unit/range-slider.spec.js
@@ -113,6 +113,21 @@ describe('RangeSlider', () => {
expect(tooltips[1].textContent).toBe('150%')
})
+ it('should sanitize tooltip content on the update path', () => {
+ fixtureEl.innerHTML = ''
+ const element = fixtureEl.querySelector('#slider')
+ const rangeSlider = new RangeSlider(element, {
+ tooltips: true,
+ value: 50,
+ tooltipsFormat: () => '
'
+ })
+
+ rangeSlider._updateTooltip(0, 60)
+
+ const inner = element.querySelector('.range-slider-tooltip-inner')
+ expect(inner.querySelector('img').hasAttribute('onerror')).toBeFalse()
+ })
+
it('should initialize with a single numeric value', () => {
fixtureEl.innerHTML = ''
const element = fixtureEl.querySelector('#slider')