164 lines
5.1 KiB
JavaScript
164 lines
5.1 KiB
JavaScript
|
import { DefaultAllowlist, sanitizeHtml } from '../../../src/util/sanitizer.js'
|
||
|
|
||
|
describe('Sanitizer', () => {
|
||
|
describe('sanitizeHtml', () => {
|
||
|
it('should return the same on empty string', () => {
|
||
|
const empty = ''
|
||
|
|
||
|
const result = sanitizeHtml(empty, DefaultAllowlist, null)
|
||
|
|
||
|
expect(result).toEqual(empty)
|
||
|
})
|
||
|
|
||
|
it('should retain tags with valid URLs', () => {
|
||
|
const validUrls = [
|
||
|
'',
|
||
|
'http://abc',
|
||
|
'HTTP://abc',
|
||
|
'https://abc',
|
||
|
'HTTPS://abc',
|
||
|
'ftp://abc',
|
||
|
'FTP://abc',
|
||
|
'mailto:me@example.com',
|
||
|
'MAILTO:me@example.com',
|
||
|
'tel:123-123-1234',
|
||
|
'TEL:123-123-1234',
|
||
|
'sip:me@example.com',
|
||
|
'SIP:me@example.com',
|
||
|
'#anchor',
|
||
|
'/page1.md',
|
||
|
'http://JavaScript/my.js',
|
||
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/', // Truncated.
|
||
|
'data:video/webm;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||
|
'data:audio/opus;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/',
|
||
|
'unknown-scheme:abc'
|
||
|
]
|
||
|
|
||
|
for (const url of validUrls) {
|
||
|
const template = [
|
||
|
'<div>',
|
||
|
` <a href="${url}">Click me</a>`,
|
||
|
' <span>Some content</span>',
|
||
|
'</div>'
|
||
|
].join('')
|
||
|
|
||
|
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||
|
|
||
|
expect(result).toContain(`href="${url}"`)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
it('should sanitize template by removing tags with XSS', () => {
|
||
|
const invalidUrls = [
|
||
|
// eslint-disable-next-line no-script-url
|
||
|
'javascript:alert(7)',
|
||
|
// eslint-disable-next-line no-script-url
|
||
|
'javascript:evil()',
|
||
|
// eslint-disable-next-line no-script-url
|
||
|
'JavaScript:abc',
|
||
|
' javascript:abc',
|
||
|
' \n Java\n Script:abc',
|
||
|
'javascript:',
|
||
|
'javascript:',
|
||
|
'j avascript:',
|
||
|
'javascript:',
|
||
|
'javascript:',
|
||
|
'jav	ascript:alert();',
|
||
|
'jav\u0000ascript:alert();'
|
||
|
]
|
||
|
|
||
|
for (const url of invalidUrls) {
|
||
|
const template = [
|
||
|
'<div>',
|
||
|
` <a href="${url}">Click me</a>`,
|
||
|
' <span>Some content</span>',
|
||
|
'</div>'
|
||
|
].join('')
|
||
|
|
||
|
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||
|
|
||
|
expect(result).not.toContain(`href="${url}"`)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
it('should sanitize template and work with multiple regex', () => {
|
||
|
const template = [
|
||
|
'<div>',
|
||
|
' <a href="javascript:alert(7)" aria-label="This is a link" data-foo="bar">Click me</a>',
|
||
|
' <span>Some content</span>',
|
||
|
'</div>'
|
||
|
].join('')
|
||
|
|
||
|
const myDefaultAllowList = DefaultAllowlist
|
||
|
// With the default allow list
|
||
|
let result = sanitizeHtml(template, myDefaultAllowList, null)
|
||
|
|
||
|
// `data-foo` won't be present
|
||
|
expect(result).not.toContain('data-foo="bar"')
|
||
|
|
||
|
// Add the following regex too
|
||
|
myDefaultAllowList['*'].push(/^data-foo/)
|
||
|
|
||
|
result = sanitizeHtml(template, myDefaultAllowList, null)
|
||
|
|
||
|
expect(result).not.toContain('href="javascript:alert(7)') // This is in the default list
|
||
|
expect(result).toContain('aria-label="This is a link"') // This is in the default list
|
||
|
expect(result).toContain('data-foo="bar"') // We explicitly allow this
|
||
|
})
|
||
|
|
||
|
it('should allow aria attributes and safe attributes', () => {
|
||
|
const template = [
|
||
|
'<div aria-pressed="true">',
|
||
|
' <span class="test">Some content</span>',
|
||
|
'</div>'
|
||
|
].join('')
|
||
|
|
||
|
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||
|
|
||
|
expect(result).toContain('aria-pressed')
|
||
|
expect(result).toContain('class="test"')
|
||
|
})
|
||
|
|
||
|
it('should remove tags not in allowlist', () => {
|
||
|
const template = [
|
||
|
'<div>',
|
||
|
' <script>alert(7)</script>',
|
||
|
'</div>'
|
||
|
].join('')
|
||
|
|
||
|
const result = sanitizeHtml(template, DefaultAllowlist, null)
|
||
|
|
||
|
expect(result).not.toContain('<script>')
|
||
|
})
|
||
|
|
||
|
it('should not use native api to sanitize if a custom function passed', () => {
|
||
|
const template = [
|
||
|
'<div>',
|
||
|
' <span>Some content</span>',
|
||
|
'</div>'
|
||
|
].join('')
|
||
|
|
||
|
function mySanitize(htmlUnsafe) {
|
||
|
return htmlUnsafe
|
||
|
}
|
||
|
|
||
|
const spy = spyOn(DOMParser.prototype, 'parseFromString')
|
||
|
|
||
|
const result = sanitizeHtml(template, DefaultAllowlist, mySanitize)
|
||
|
|
||
|
expect(result).toEqual(template)
|
||
|
expect(spy).not.toHaveBeenCalled()
|
||
|
})
|
||
|
|
||
|
it('should allow multiple sanitation passes of the same template', () => {
|
||
|
const template = '<img src="test.jpg">'
|
||
|
|
||
|
const firstResult = sanitizeHtml(template, DefaultAllowlist, null)
|
||
|
const secondResult = sanitizeHtml(template, DefaultAllowlist, null)
|
||
|
|
||
|
expect(firstResult).toContain('src')
|
||
|
expect(secondResult).toContain('src')
|
||
|
})
|
||
|
})
|
||
|
})
|