Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion js/src/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions js/src/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -606,10 +606,10 @@ class Calendar extends BaseComponent {
navigationElement.classList.add('calendar-nav')
navigationElement.innerHTML = `
<div class="calendar-nav-prev">
<button type="button" class="calendar-nav-btn btn-double-prev" aria-label="${this._config.ariaNavPrevYearLabel}">
<button type="button" class="calendar-nav-btn btn-double-prev" aria-label="${escapeHtml(this._config.ariaNavPrevYearLabel)}">
<span class="calendar-nav-icon calendar-nav-icon-double-prev"></span>
</button>
${this._view === 'days' ? `<button type="button" class="calendar-nav-btn btn-prev" aria-label="${this._config.ariaNavPrevMonthLabel}">
${this._view === 'days' ? `<button type="button" class="calendar-nav-btn btn-prev" aria-label="${escapeHtml(this._config.ariaNavPrevMonthLabel)}">
<span class="calendar-nav-icon calendar-nav-icon-prev"></span>
</button>` : ''}
</div>
Expand All @@ -622,10 +622,10 @@ class Calendar extends BaseComponent {
</button>
</div>
<div class="calendar-nav-next">
${this._view === 'days' ? `<button type="button" class="calendar-nav-btn btn-next" aria-label="${this._config.ariaNavNextMonthLabel}">
${this._view === 'days' ? `<button type="button" class="calendar-nav-btn btn-next" aria-label="${escapeHtml(this._config.ariaNavNextMonthLabel)}">
<span class="calendar-nav-icon calendar-nav-icon-next"></span>
</button>` : ''}
<button type="button" class="calendar-nav-btn btn-double-next" aria-label="${this._config.ariaNavNextYearLabel}">
<button type="button" class="calendar-nav-btn btn-double-next" aria-label="${escapeHtml(this._config.ariaNavNextYearLabel)}">
<span class="calendar-nav-icon calendar-nav-icon-double-next"></span>
</button>
</div>
Expand All @@ -644,7 +644,7 @@ class Calendar extends BaseComponent {
${this._config.showWeekNumber ?
`<th class="${CLASS_NAME_CALENDAR_CELL}">
<div class="calendar-header-cell-inner">
${this._config.weekNumbersLabel ?? ''}
${this._config.weekNumbersLabel ? escapeHtml(this._config.weekNumbersLabel) : ''}
</div>
</th>` : ''
}
Expand Down
8 changes: 4 additions & 4 deletions js/src/date-range-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -779,7 +779,7 @@ class DateRangePicker extends BaseComponent {
this._calendar.update(this._getCalendarConfig())
})

buttonEl.innerHTML = key
buttonEl.textContent = key
dateRangePickerRangesEl.append(buttonEl)
}

Expand Down Expand Up @@ -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
Expand All @@ -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()
})
Expand All @@ -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()
})
Expand Down
4 changes: 3 additions & 1 deletion js/src/range-slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions js/src/time-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
Expand All @@ -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()
})
Expand Down
17 changes: 17 additions & 0 deletions js/tests/unit/autocomplete.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,23 @@ describe('Autocomplete', () => {
expect(autocomplete._isShown()).toBe(true)
})

it('should render searchNoResultsLabel as text, not markup', () => {
fixtureEl.innerHTML = '<div class="autocomplete"></div>'
const autocompleteEl = fixtureEl.querySelector('.autocomplete')
const autocomplete = new Autocomplete(autocompleteEl, {
searchNoResultsLabel: '<img src=x onerror="window.xss = true">',
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('<img src=x onerror="window.xss = true">')
})

it('should show with container mode', () => {
fixtureEl.innerHTML = '<div class="autocomplete"></div><div id="container"></div>'
const autocompleteEl = fixtureEl.querySelector('.autocomplete')
Expand Down
25 changes: 25 additions & 0 deletions js/tests/unit/calendar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<div></div>'

const div = fixtureEl.querySelector('div')
new Calendar(div, { // eslint-disable-line no-new
ariaNavNextYearLabel: '"><img src=x onerror="window.xss = true">'
})

expect(div.querySelector('.calendar-nav img')).toBeNull()
expect(div.querySelector('.btn-double-next').getAttribute('aria-label'))
.toEqual('"><img src=x onerror="window.xss = true">')
})

it('should escape the week numbers label', () => {
fixtureEl.innerHTML = '<div></div>'

const div = fixtureEl.querySelector('div')
new Calendar(div, { // eslint-disable-line no-new
showWeekNumber: true,
weekNumbersLabel: '<img src=x onerror="window.xss = true">'
})

expect(div.querySelector('.calendar-header-cell-inner img')).toBeNull()
})
})
})
15 changes: 15 additions & 0 deletions js/tests/unit/range-slider.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ describe('RangeSlider', () => {
expect(tooltips[1].textContent).toBe('150%')
})

it('should sanitize tooltip content on the update path', () => {
fixtureEl.innerHTML = '<div id="slider"></div>'
const element = fixtureEl.querySelector('#slider')
const rangeSlider = new RangeSlider(element, {
tooltips: true,
value: 50,
tooltipsFormat: () => '<img src=x onerror="window.xss = true">'
})

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 = '<div id="slider"></div>'
const element = fixtureEl.querySelector('#slider')
Expand Down
Loading