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
33 changes: 2 additions & 31 deletions js/src/rating.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,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 { sanitizeHtml, SVGAllowlist } from './util/sanitizer.js'
import { defineJQueryPlugin, getUID } from './util/index.js'
import Tooltip from './tooltip.js'

Expand Down Expand Up @@ -47,39 +47,10 @@ const SELECTOR_DATA_TOGGLE = '[data-coreui-toggle="rating"]'
const SELECTOR_RATING_ITEM_INPUT = '.rating-item-input'
const SELECTOR_RATING_ITEM_LABEL = '.rating-item-label'

// js-docs-start svg-allow-list
export const svgAllowList = {
...DefaultAllowlist,
svg: ['xmlns', 'version', 'baseprofile', 'width', 'height', 'viewbox', 'preserveaspectratio', 'aria-hidden', 'role', 'focusable'],
g: ['id', 'class', 'transform', 'style'],
path: ['id', 'class', 'd', 'fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity'],
circle: ['id', 'class', 'cx', 'cy', 'r', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
rect: ['id', 'class', 'x', 'y', 'width', 'height', 'rx', 'ry', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
ellipse: ['id', 'class', 'cx', 'cy', 'rx', 'ry', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
line: ['id', 'class', 'x1', 'y1', 'x2', 'y2', 'stroke', 'stroke-width', 'stroke-opacity'],
polygon: ['id', 'class', 'points', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
polyline: ['id', 'class', 'points', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
text: ['id', 'class', 'x', 'y', 'dx', 'dy', 'text-anchor', 'font-family', 'font-size', 'font-weight', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
tspan: ['id', 'class', 'x', 'y', 'dx', 'dy', 'text-anchor', 'font-family', 'font-size', 'font-weight', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
defs: [],
symbol: ['id', 'class', 'viewbox', 'preserveaspectratio'],
use: ['id', 'class', 'x', 'y', 'width', 'height', 'href'],
image: ['id', 'class', 'x', 'y', 'width', 'height', 'href', 'preserveaspectratio', 'xlink:href'],
pattern: ['id', 'class', 'x', 'y', 'width', 'height', 'patternunits', 'patterncontentunits', 'patterntransform', 'preserveaspectratio'],
lineargradient: ['id', 'class', 'gradientunits', 'x1', 'y1', 'x2', 'y2', 'spreadmethod', 'gradienttransform'],
radialgradient: ['id', 'class', 'gradientunits', 'cx', 'cy', 'r', 'fx', 'fy', 'spreadmethod', 'gradienttransform'],
mask: ['id', 'class', 'x', 'y', 'width', 'height', 'maskunits', 'maskcontentunits', 'masktransform'],
clippath: ['id', 'class', 'clippathunits'],
marker: ['id', 'class', 'markerunits', 'markerwidth', 'markerheight', 'orient', 'preserveaspectratio', 'viewbox', 'refx', 'refy'],
title: [],
desc: []
}
// js-docs-end svg-allow-list

const Default = {
activeIcon: null,
allowClear: false,
allowList: svgAllowList,
allowList: SVGAllowlist,
disabled: false,
highlightOnlySelected: false,
icon: null,
Expand Down
41 changes: 41 additions & 0 deletions js/src/util/sanitizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,47 @@ export const DefaultAllowlist = {
}
// js-docs-end allow-list

// js-docs-start svg-allow-list
export const SVGAllowlist = {
...DefaultAllowlist,
svg: ['xmlns', 'version', 'baseprofile', 'width', 'height', 'viewbox', 'preserveaspectratio', 'aria-hidden', 'role', 'focusable', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin'],
g: ['id', 'class', 'transform', 'style'],
path: ['id', 'class', 'd', 'fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-opacity'],
circle: ['id', 'class', 'cx', 'cy', 'r', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
rect: ['id', 'class', 'x', 'y', 'width', 'height', 'rx', 'ry', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
ellipse: ['id', 'class', 'cx', 'cy', 'rx', 'ry', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
line: ['id', 'class', 'x1', 'y1', 'x2', 'y2', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-opacity'],
polygon: ['id', 'class', 'points', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
polyline: ['id', 'class', 'points', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
text: ['id', 'class', 'x', 'y', 'dx', 'dy', 'text-anchor', 'font-family', 'font-size', 'font-weight', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
tspan: ['id', 'class', 'x', 'y', 'dx', 'dy', 'text-anchor', 'font-family', 'font-size', 'font-weight', 'fill', 'fill-opacity', 'stroke', 'stroke-width', 'stroke-opacity'],
defs: [],
symbol: ['id', 'class', 'viewbox', 'preserveaspectratio'],
use: ['id', 'class', 'x', 'y', 'width', 'height', 'href'],
image: ['id', 'class', 'x', 'y', 'width', 'height', 'href', 'preserveaspectratio', 'xlink:href'],
pattern: ['id', 'class', 'x', 'y', 'width', 'height', 'patternunits', 'patterncontentunits', 'patterntransform', 'preserveaspectratio'],
lineargradient: ['id', 'class', 'gradientunits', 'x1', 'y1', 'x2', 'y2', 'spreadmethod', 'gradienttransform'],
radialgradient: ['id', 'class', 'gradientunits', 'cx', 'cy', 'r', 'fx', 'fy', 'spreadmethod', 'gradienttransform'],
mask: ['id', 'class', 'x', 'y', 'width', 'height', 'maskunits', 'maskcontentunits', 'masktransform'],
clippath: ['id', 'class', 'clippathunits'],
marker: ['id', 'class', 'markerunits', 'markerwidth', 'markerheight', 'orient', 'preserveaspectratio', 'viewbox', 'refx', 'refy'],
title: [],
desc: []
}
// js-docs-end svg-allow-list

const ESCAPE_HTML_MAP = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;'
}

export function escapeHtml(unsafeText) {
return String(unsafeText).replace(/[&<>"']/g, character => ESCAPE_HTML_MAP[character])
}

const uriAttributes = new Set([
'background',
'cite',
Expand Down
24 changes: 23 additions & 1 deletion js/tests/unit/util/sanitizer.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer.js'
import {
DefaultAllowlist, escapeHtml, sanitizeHtml, SVGAllowlist
} from '../../../src/util/sanitizer.js'

describe('Sanitizer', () => {
describe('escapeHtml', () => {
it('should escape HTML-significant characters', () => {
expect(escapeHtml('<img src="x" onerror=\'alert(1)\'> & more'))
.toEqual('&lt;img src=&quot;x&quot; onerror=&#x27;alert(1)&#x27;&gt; &amp; more')
})

it('should leave plain text untouched', () => {
expect(escapeHtml('Water and Fire')).toEqual('Water and Fire')
})
})

describe('SVGAllowlist', () => {
it('should extend the default allow list with SVG elements', () => {
expect(SVGAllowlist.div).toEqual(DefaultAllowlist.div)
expect(SVGAllowlist.svg).toContain('viewbox')
expect(SVGAllowlist.path).toContain('d')
expect(SVGAllowlist.line).toContain('x1')
})
})

describe('sanitizeHtml', () => {
it('should return the same on empty string', () => {
const empty = ''
Expand Down
Loading