blob: 29560a134018d7f5faa372bcd6602fa2ab6b4afb [file] [log] [blame] [edit]
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory())
: typeof define === 'function' && define.amd
? define(factory)
: ((global =
typeof globalThis !== 'undefined' ? globalThis : global || self),
(global.testSuite = factory()));
})(this, function () {
return function testSuite(
DOMPurify,
window,
sanitizationTestCases,
xssTestCases
) {
var document = window.document;
var jQuery = window.jQuery;
sanitizationTestCases.forEach((testCase) => {
QUnit.test(`Sanitization test[${testCase.title}]`, (assert) => {
assert.contains(
DOMPurify.sanitize(testCase.payload),
testCase.expected,
`Payload: ${testCase.payload}`
);
});
});
// XSS tests: Native DOM methods (alert() should not be called)
xssTestCases.forEach((testCase) => {
QUnit.test(`XSS test: native[${testCase.title}]`, (assert) => {
document.getElementById('qunit-fixture').innerHTML = DOMPurify.sanitize(
testCase.payload
);
const done = assert.async();
setTimeout(() => {
assert.notEqual(window.xssed, true, 'alert() was called');
// Teardown
document.getElementById('qunit-fixture').innerHTML = '';
window.xssed = false;
done();
}, 100);
});
});
// XSS tests: jQuery (alert() should not be called)
xssTestCases.forEach((testCase) => {
QUnit.test(`XSS test: jQuery[${testCase.title}]`, (assert) => {
jQuery('#qunit-fixture').html(DOMPurify.sanitize(testCase.payload));
const done = assert.async();
setTimeout(() => {
assert.notEqual(window.xssed, true, 'alert() was called');
// Teardown
jQuery('#qunit-fixture').empty();
window.xssed = false;
done();
}, 100);
});
});
// document.write tests to handle FF's strange behavior
xssTestCases.forEach((testCase) => {
QUnit.test(
`XSS test: document.write() into iframe[${testCase.title}]`,
(assert) => {
const done = assert.async();
const iframe = document.createElement('iframe');
iframe.src = 'about:blank';
iframe.onload = function () {
iframe.contentDocument.write(
'<script>window.alert=function(){top.xssed=true;}</script>' +
DOMPurify.sanitize(testCase.payload)
);
assert.notEqual(
window.xssed,
true,
'alert() was called from document.write()'
);
window.xssed = false;
iframe.parentNode.removeChild(iframe);
done();
};
document.body.appendChild(iframe);
}
);
});
// Config-Flag Tests
QUnit.test(
'Config-Flag tests: KEEP_CONTENT + ALLOWED_TAGS / ALLOWED_ATTR',
function (assert) {
// KEEP_CONTENT + ALLOWED_TAGS / ALLOWED_ATTR
assert.equal(
DOMPurify.sanitize('<iframe>Hello</iframe>', { KEEP_CONTENT: false }),
''
);
assert.contains(
DOMPurify.sanitize(
'<a href="#">abc<b style="color:red">123</b><q class="cite">123</b></a>',
{
ALLOWED_TAGS: ['b', 'q'],
ALLOWED_ATTR: ['style'],
KEEP_CONTENT: true,
}
),
[
'abc<b style="color:red">123</b><q>123</q>',
'abc<b style="color: red;">123</b><q>123</q>',
]
);
assert.equal(
DOMPurify.sanitize(
'<a href="#">abc<b style="color:red">123</b><q class="cite">123</b></a>',
{
ALLOWED_TAGS: ['b', 'q'],
ALLOWED_ATTR: ['style'],
KEEP_CONTENT: false,
}
),
''
);
assert.equal(
DOMPurify.sanitize('<a href="#">abc</a>', {
ALLOWED_TAGS: ['b', 'q'],
KEEP_CONTENT: false,
}),
''
);
assert.equal(
DOMPurify.sanitize('<form><input name="parentNode"></form>', {
ALLOWED_TAGS: ['input'],
KEEP_CONTENT: true,
}),
'<input>'
);
}
);
QUnit.test('Config-Flag tests: ALLOW_SELF_CLOSE_IN_ATTR', function (assert) {
// ALLOW_SELF_CLOSE_IN_ATTR
assert.equal(
DOMPurify.sanitize('<a href="#" class="foo <br/>">abc</a>', {
ALLOW_SELF_CLOSE_IN_ATTR: false,
}),
'<a href="#">abc</a>'
);
assert.equal(
DOMPurify.sanitize('<a href="#" class="foo <br/>">abc</a>', {
ALLOW_SELF_CLOSE_IN_ATTR: true,
}),
'<a class="foo <br/>" href="#">abc</a>'
);
});
QUnit.test('Config-Flag tests: ALLOW_DATA_ATTR', function (assert) {
// ALLOW_DATA_ATTR
assert.equal(
DOMPurify.sanitize('<a href="#" data-abc"="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
}),
'<a href="#">abc</a>'
);
assert.equal(
DOMPurify.sanitize('<a href="#" data-abc="foo">abc</a>', {
ALLOW_DATA_ATTR: false,
}),
'<a href="#">abc</a>'
);
assert.contains(
DOMPurify.sanitize('<a href="#" data-abc="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
}),
[
'<a data-abc="foo" href="#">abc</a>',
'<a href="#" data-abc="foo">abc</a>',
]
);
assert.contains(
DOMPurify.sanitize('<a href="#" data-abc-1-2-3="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
}),
[
'<a data-abc-1-2-3="foo" href="#">abc</a>',
'<a href="#" data-abc-1-2-3="foo">abc</a>',
]
);
assert.equal(
DOMPurify.sanitize('<a href="#" data-""="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
}),
'<a href="#">abc</a>'
);
assert.contains(
DOMPurify.sanitize('<a href="#" data-äöü="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
}),
[
'<a href="#" data-äöü="foo">abc</a>',
'<a data-äöü="foo" href="#">abc</a>',
]
);
assert.contains(
DOMPurify.sanitize('<a href="#" data-\u00B7._="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
}),
[
'<a data-\u00B7._="foo" href="#">abc</a>',
'<a href="#">abc</a>',
'<a href="#" data-·._="foo">abc</a>',
] // IE11 and Edge throw an InvalidCharacterError
);
assert.equal(
DOMPurify.sanitize('<a href="#" data-\u00B5="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
}),
'<a href="#">abc</a>'
);
assert.equal(
DOMPurify.sanitize('<a href="#" data-evil="foo">abc</a>', {
FORBID_ATTR: ['data-evil'],
}),
'<a href="#">abc</a>'
);
assert.equal(
DOMPurify.sanitize('<a href="#" data-evil="foo">abc</a>', {
ALLOW_DATA_ATTR: true,
FORBID_ATTR: ['data-evil'],
}),
'<a href="#">abc</a>'
);
});
QUnit.test('Config-Flag tests: ADD_TAGS', function (assert) {
// ADD_TAGS
assert.equal(
DOMPurify.sanitize('<my-component>abc</my-component>', {
ADD_TAGS: ['my-component'],
}),
'<my-component>abc</my-component>'
);
});
QUnit.test('Config-Flag tests: ADD_TAGS + ADD_ATTR', function (assert) {
// ADD_TAGS + ADD_ATTR
assert.equal(
DOMPurify.sanitize('<my-component my-attr="foo">abc</my-component>', {
ADD_TAGS: ['my-component'],
}),
'<my-component>abc</my-component>'
);
assert.equal(
DOMPurify.sanitize('<my-component my-attr="foo">abc</my-component>', {
ADD_TAGS: ['my-component'],
ADD_ATTR: ['my-attr'],
}),
'<my-component my-attr="foo">abc</my-component>'
);
});
QUnit.test(
'Config-Flag tests: FORBID_CONTENTS + FORBID_TAGS',
function (assert) {
// FORBID_CONTENTS + FORBID_TAGS
assert.equal(
DOMPurify.sanitize(
'<div><b>preserve me</b></div><p><b>no not preserve me</b></p>',
{ FORBID_CONTENTS: ['p'], FORBID_TAGS: ['div', 'p'] }
),
'<b>preserve me</b>'
);
}
);
QUnit.test(
'Config-Flag tests: SAFE_FOR_JQUERY (now inactive, secure by default)',
function (assert) {
assert.equal(
DOMPurify.sanitize(
'<a>123</a><option><style><img src=x onerror=alert(1)>'
),
'<a>123</a><option></option>'
);
assert.equal(
DOMPurify.sanitize(
'<a>123</a><option><style><img src=x onerror=alert(1)>'
),
'<a>123</a><option></option>'
);
assert.equal(
DOMPurify.sanitize(
'<option><style></option></select><b><img src=xx: onerror=alert(1)></style></option>'
),
'<option></option>'
);
assert.equal(
DOMPurify.sanitize(
'<option><iframe></select><b><script>alert(1)</script>'
),
'<option></option>'
);
assert.equal(
DOMPurify.sanitize(
'<option><iframe></select><b><script>alert(1)</script>'
),
'<option></option>'
);
assert.equal(
DOMPurify.sanitize(
'<b><style><style/><img src=xx: onerror=alert(1)>'
),
'<b></b>'
);
assert.equal(
DOMPurify.sanitize(
'<b><style><style/><img src=xx: onerror=alert(1)>'
),
'<b></b>'
);
assert.contains(
DOMPurify.sanitize('1<template><s>000</s></template>2'),
['1<template><s>000</s></template>2', '1<template></template>2', '12']
);
assert.contains(DOMPurify.sanitize('<template><s>000</s></template>'), [
'',
'<template><s>000</s></template>',
]);
// see https://github.com/cure53/DOMPurify/issues/283
assert.equal(
DOMPurify.sanitize('<i>&amp;amp; &lt;</i>'),
'<i>&amp;amp; &lt;</i>'
);
}
);
QUnit.test('Config-Flag tests: SAFE_FOR_TEMPLATES', function (assert) {
//SAFE_FOR_TEMPLATES
assert.equal(
DOMPurify.sanitize(
'<a>123{{456}}<b><style><% alert(1) %></style>456</b></a>',
{ SAFE_FOR_TEMPLATES: true }
),
'<a> <b><style> </style>456</b></a>'
);
assert.equal(
DOMPurify.sanitize('<a data-bind="style: alert(1)"></a>', {
SAFE_FOR_TEMPLATES: true,
}),
'<a></a>'
);
assert.equal(
DOMPurify.sanitize('<a data-harmless=""></a>', {
SAFE_FOR_TEMPLATES: true,
ALLOW_DATA_ATTR: true,
}),
'<a></a>'
);
assert.equal(
DOMPurify.sanitize('<a data-harmless=""></a>', {
SAFE_FOR_TEMPLATES: false,
ALLOW_DATA_ATTR: false,
}),
'<a></a>'
);
assert.equal(
DOMPurify.sanitize(
'<a>{{123}}{{456}}<b><style><% alert(1) %><% 123 %></style>456</b></a>',
{ SAFE_FOR_TEMPLATES: true }
),
'<a> <b><style> </style>456</b></a>'
);
assert.equal(
DOMPurify.sanitize(
'<a>{{123}}abc{{456}}<b><style><% alert(1) %>def<% 123 %></style>456</b></a>',
{ SAFE_FOR_TEMPLATES: true }
),
'<a> <b><style> </style>456</b></a>'
);
assert.equal(
DOMPurify.sanitize(
'<a>123{{45{{6}}<b><style><% alert(1)%> %></style>456</b></a>',
{ SAFE_FOR_TEMPLATES: true }
),
'<a> <b><style> </style>456</b></a>'
);
assert.equal(
DOMPurify.sanitize(
'<a>123{{45}}6}}<b><style><% <%alert(1) %></style>456</b></a>',
{ SAFE_FOR_TEMPLATES: true }
),
'<a> <b><style> </style>456</b></a>'
);
assert.equal(
DOMPurify.sanitize(
'<a>123{{<b>456}}</b><style><% alert(1) %></style>456</a>',
{ SAFE_FOR_TEMPLATES: true }
),
'<a>123 <b> </b><style> </style>456</a>'
);
assert.contains(
DOMPurify.sanitize(
'<b>{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}</b>',
{ SAFE_FOR_TEMPLATES: true }
),
['<b> </b>', '<b> </b>', '<b> <form><img src="x"></form> </b>']
);
assert.contains(
DOMPurify.sanitize(
'<b>he{{evil<script>alert(1)</script><form><img src=x name=textContent></form>}}ya</b>',
{ SAFE_FOR_TEMPLATES: true }
),
[
'<b>he ya</b>',
'<b>he </b>',
'<b>he <form><img src="x"></form> ya</b>',
]
);
assert.equal(
DOMPurify.sanitize(
'<a>123<% <b>456}}</b><style>{{ alert(1) }}</style>456 %></a>',
{ SAFE_FOR_TEMPLATES: true }
),
'<a>123 <b> </b><style> </style> </a>'
);
assert.equal(
DOMPurify.sanitize('<a href="}}javascript:alert(1)"></a>', {
SAFE_FOR_TEMPLATES: true,
}),
'<a></a>'
);
assert.equal(
DOMPurify.sanitize('<a class="{{999-333}}"></a>', {
SAFE_FOR_TEMPLATES: true,
}),
'<a class=" "></a>'
);
assert.equal(
DOMPurify.sanitize('{{999-333}}', { SAFE_FOR_TEMPLATES: true }),
' '
);
assert.equal(
DOMPurify.sanitize('{<x>{333+333}<x>}', { SAFE_FOR_TEMPLATES: true }),
' '
);
});
QUnit.test('Config-Flag tests: SANITIZE_DOM', function (assert) {
// SANITIZE_DOM
assert.equal(
DOMPurify.sanitize('<img src="x" name="implementation">', {
SANITIZE_DOM: true,
}),
'<img src="x">'
);
assert.equal(
DOMPurify.sanitize('<img src="x" name="createNodeIterator">', {
SANITIZE_DOM: true,
}),
'<img src="x">'
);
assert.equal(
DOMPurify.sanitize('<img src="x" name="getElementById">', {
SANITIZE_DOM: false,
}),
'<img name="getElementById" src="x">'
);
assert.equal(
DOMPurify.sanitize('<img src="x" name="getElementById">', {
SANITIZE_DOM: true,
}),
'<img src="x">'
);
assert.equal(
DOMPurify.sanitize('<a href="x" id="location">click</a>', {
SANITIZE_DOM: true,
}),
'<a href="x">click</a>'
);
assert.contains(
DOMPurify.sanitize('<form><input name="attributes"></form>', {
ADD_TAGS: ['form'],
SANITIZE_DOM: false,
}),
['', '<form><input name="attributes"></form>']
);
assert.contains(
DOMPurify.sanitize('<form><input name="attributes"></form>', {
ADD_TAGS: ['form'],
SANITIZE_DOM: true,
}),
['', '<form><input name="attributes"></form>', '<form><input></form>']
);
});
QUnit.test('Config-Flag tests: SANITIZE_NAMED_PROPS', function (assert) {
// SANITIZE_NAMED_PROPS
assert.equal(
DOMPurify.sanitize('<a id="x"></a>', {
SANITIZE_NAMED_PROPS: true,
}),
'<a id="user-content-x"></a>'
);
assert.equal(
DOMPurify.sanitize('<form id="x"><input id="y"></form>', {
SANITIZE_NAMED_PROPS: true,
}),
'<form id="user-content-x"><input id="user-content-y"></form>'
);
assert.equal(
DOMPurify.sanitize('<a id="x"></a><a id="x"></a>', {
SANITIZE_NAMED_PROPS: true,
}),
'<a id="user-content-x"></a><a id="user-content-x"></a>'
);
});
QUnit.test('Config-Flag tests: WHOLE_DOCUMENT', function (assert) {
//WHOLE_DOCUMENT
assert.equal(DOMPurify.sanitize('123', { WHOLE_DOCUMENT: false }), '123');
assert.equal(
DOMPurify.sanitize('123', { WHOLE_DOCUMENT: true }),
'<html><head></head><body>123</body></html>'
);
assert.equal(
DOMPurify.sanitize('<style>*{color:red}</style>', {
WHOLE_DOCUMENT: false,
}),
''
);
assert.equal(
DOMPurify.sanitize('<style>*{color:red}</style>', {
WHOLE_DOCUMENT: true,
}),
'<html><head><style>*{color:red}</style></head><body></body></html>'
);
assert.equal(
DOMPurify.sanitize('123<style>*{color:red}</style>', {
WHOLE_DOCUMENT: false,
}),
'123<style>*{color:red}</style>'
);
assert.equal(
DOMPurify.sanitize('123<style>*{color:red}</style>', {
WHOLE_DOCUMENT: true,
}),
'<html><head></head><body>123<style>*{color:red}</style></body></html>'
);
assert.equal(
DOMPurify.sanitize('<!DOCTYPE html><html><body>123</body></html>', {
WHOLE_DOCUMENT: true,
}),
'<html><head></head><body>123</body></html>'
);
assert.equal(
DOMPurify.sanitize('<!DOCTYPE html><html><body>123</body></html>', {
WHOLE_DOCUMENT: true,
ADD_TAGS: ['!doctype'],
}),
'<!DOCTYPE html>\n<html><head></head><body>123</body></html>'
);
});
QUnit.test('Config-Flag tests: RETURN_DOM', function (assert) {
//RETURN_DOM
assert.equal(
DOMPurify.sanitize('<a>123<b>456</b></a>', { RETURN_DOM: true })
.outerHTML,
'<body><a>123<b>456</b></a></body>'
);
assert.equal(
DOMPurify.sanitize('<a>123<b>456<script>alert(1)</script></b></a>', {
RETURN_DOM: true,
}).outerHTML,
'<body><a>123<b>456</b></a></body>'
);
assert.equal(
DOMPurify.sanitize('<a>123<b>456</b></a>', {
RETURN_DOM: true,
WHOLE_DOCUMENT: true,
}).outerHTML,
'<html><head></head><body><a>123<b>456</b></a></body></html>'
);
assert.equal(
DOMPurify.sanitize('<a>123<b>456<script>alert(1)</script></b></a>', {
RETURN_DOM: true,
WHOLE_DOCUMENT: true,
}).outerHTML,
'<html><head></head><body><a>123<b>456</b></a></body></html>'
);
assert.equal(
DOMPurify.sanitize('123', { RETURN_DOM: true }).outerHTML,
'<body>123</body>'
);
});
QUnit.test('Config-Flag tests: shadowroot', function (assert) {
assert.notEqual(
DOMPurify.sanitize('123', {
RETURN_DOM: true,
}).ownerDocument,
document
);
assert.equal(
DOMPurify.sanitize('123', {
RETURN_DOM: true,
ADD_ATTR: ['shadowroot'],
}).ownerDocument,
document
);
assert.notEqual(
DOMPurify.sanitize('123', {
RETURN_DOM_FRAGMENT: true,
}).ownerDocument,
document
);
assert.equal(
DOMPurify.sanitize('123', {
RETURN_DOM_FRAGMENT: true,
ADD_ATTR: ['shadowroot'],
}).ownerDocument,
document
);
});
QUnit.test('Config-Flag tests: RETURN_DOM_FRAGMENT', function (assert) {
//RETURN_DOM_FRAGMENT
// attempt clobbering
var fragment = DOMPurify.sanitize(
'foo<img id="createDocumentFragment">',
{
RETURN_DOM_FRAGMENT: true,
}
);
assert.equal(fragment.nodeType, 11);
assert.notEqual(fragment.ownerDocument, document);
assert.equal(fragment.firstChild && fragment.firstChild.nodeValue, 'foo');
// again, but without SANITIZE_DOM
fragment = DOMPurify.sanitize('foo<img id="createDocumentFragment">', {
RETURN_DOM_FRAGMENT: true,
SANITIZE_DOM: false,
});
assert.equal(fragment.nodeType, 11);
assert.notEqual(fragment.ownerDocument, document);
assert.equal(fragment.firstChild && fragment.firstChild.nodeValue, 'foo');
});
QUnit.test('Config-Flag tests: RETURN_DOM_FRAGMENT', function (assert) {
var xss = `<body><div><template shadowroot=open><img src=x onerror=alert(3)></template></div></body>`;
var dom_body = DOMPurify.sanitize(xss, { RETURN_DOM: true });
assert.equal(
dom_body.outerHTML,
'<body><div><template><img src="x"></template></div></body>'
);
});
QUnit.test('Config-Flag tests: IN_PLACE', function (assert) {
//IN_PLACE
var dirty = document.createElement('a');
dirty.setAttribute('href', 'javascript:alert(1)');
var clean = DOMPurify.sanitize(dirty, { IN_PLACE: true });
assert.equal(dirty, clean); // should return the input node
assert.equal(dirty.href, ''); // should still sanitize
});
QUnit.test(
'Config-Flag tests: IN_PLACE insecure root-nodes',
function (assert) {
//IN_PLACE with insecure root node (script)
var dirty = document.createElement('script');
dirty.setAttribute('src', 'data:,alert(1)');
assert.throws(function () {
DOMPurify.sanitize(dirty, { IN_PLACE: true });
});
}
);
QUnit.test(
'Config-Flag tests: IN_PLACE insecure root-nodes',
function (assert) {
//IN_PLACE with insecure root node (iframe)
var dirty = document.createElement('iframe');
dirty.setAttribute('src', 'javascript:alert(1)');
assert.throws(function () {
DOMPurify.sanitize(dirty, { IN_PLACE: true });
});
}
);
QUnit.test('Config-Flag tests: FORBID_TAGS', function (assert) {
//FORBID_TAGS
assert.equal(
DOMPurify.sanitize('<a>123<b>456</b></a>', { FORBID_TAGS: ['b'] }),
'<a>123456</a>'
);
assert.equal(
DOMPurify.sanitize('<a>123<b>456<script>alert(1)</script></b></a>789', {
FORBID_TAGS: ['a', 'b'],
}),
'123456789'
);
assert.equal(
DOMPurify.sanitize('<a>123<b>456</b></a>', { FORBID_TAGS: ['c'] }),
'<a>123<b>456</b></a>'
);
assert.equal(
DOMPurify.sanitize('<a>123<b>456<script>alert(1)</script></b></a>789', {
FORBID_TAGS: ['script', 'b'],
}),
'<a>123456</a>789'
);
assert.equal(
DOMPurify.sanitize('<a>123<b>456</b></a>', {
ADD_TAGS: ['b'],
FORBID_TAGS: ['b'],
}),
'<a>123456</a>'
);
});
QUnit.test('Config-Flag tests: FORBID_ATTR', function (assert) {
//FORBID_ATTR
assert.equal(
DOMPurify.sanitize('<a x="1">123<b>456</b></a>', {
FORBID_ATTR: ['x'],
}),
'<a>123<b>456</b></a>'
);
assert.equal(
DOMPurify.sanitize(
'<a class="0" x="1">123<b y="1">456<script>alert(1)</script></b></a>789',
{ FORBID_ATTR: ['x', 'y'] }
),
'<a class="0">123<b>456</b></a>789'
);
assert.equal(
DOMPurify.sanitize('<a y="1">123<b y="1" y="2">456</b></a>', {
FORBID_ATTR: ['y'],
}),
'<a>123<b>456</b></a>'
);
assert.equal(
DOMPurify.sanitize(
'<a>123<b x="1">456<script y="1">alert(1)</script></b></a>789',
{ FORBID_ATTR: ['x', 'y'] }
),
'<a>123<b>456</b></a>789'
);
});
QUnit.test(
'Config-Param tests: CUSTOM_ELEMENT_HANDLING',
function (assert) {
//CUSTOM_ELEMENT_HANDLING
assert.equal(
DOMPurify.sanitize(
'<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
{
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: /^foo-/,
attributeNameCheck: /baz/,
allowCustomizedBuiltInElements: true,
},
}
),
'<foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>'
);
assert.equal(
DOMPurify.sanitize(
'<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
{
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: /^foo-/,
attributeNameCheck: /baz/,
allowCustomizedBuiltInElements: false,
},
}
),
'<foo-bar baz="foobar"></foo-bar><div is=""></div>'
);
assert.equal(
DOMPurify.sanitize(
'<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
{
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: /-bar$/,
attributeNameCheck: /.+/,
allowCustomizedBuiltInElements: true,
},
}
), // DOMPurify swaps the order of attributes here!
'<foo-bar forbidden="true" baz="foobar"></foo-bar><div is=""></div>'
);
assert.equal(
DOMPurify.sanitize(
'<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
{
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: (tagName) => tagName.match(/^foo-/),
attributeNameCheck: (attr) => attr.match(/baz/),
allowCustomizedBuiltInElements: true,
},
}
),
'<foo-bar baz="foobar"></foo-bar><div is="foo-baz"></div>'
);
assert.equal(
DOMPurify.sanitize(
'<foo-bar baz="foobar" forbidden="true"></foo-bar><div is="foo-baz"></div>',
{
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: (tagName) => tagName.match(/-bar$/),
attributeNameCheck: (attr) => attr.match(/baz/),
allowCustomizedBuiltInElements: true,
},
}
),
'<foo-bar baz="foobar"></foo-bar><div is=""></div>'
);
assert.equal(
DOMPurify.sanitize(
'<my-paragraph><span slot="my-text">test</span></my-paragraph>',
{
CUSTOM_ELEMENT_HANDLING: { tagNameCheck: /-/u },
}
),
'<my-paragraph><span slot="my-text">test</span></my-paragraph>'
);
}
);
QUnit.test(
'CUSTOM_ELEMENT_HANDLING config values of null do not throw a TypeError.',
function (assert) {
DOMPurify.sanitize('', {
CUSTOM_ELEMENT_HANDLING: {
tagNameCheck: null,
attributeNameCheck: null,
allowCustomizedBuiltInElements: null,
},
});
// Don't see a great way to assert NOT throws...
assert.ok(true);
}
);
QUnit.test('Test dirty being an array', function (assert) {
assert.equal(
DOMPurify.sanitize(['<a>123<b>456</b></a>']),
'<a>123<b>456</b></a>'
);
assert.equal(
DOMPurify.sanitize(['<img src=', 'x onerror=alert(1)>']),
'<img src=",x">'
);
});
// cross-check that document.write into iframe works properly
QUnit.test('XSS test: document.write() into iframe', function (assert) {
const done = assert.async();
window.xssed = false;
var iframe = document.createElement('iframe');
iframe.src = 'about:blank';
iframe.onload = function () {
iframe.contentDocument.write(
'<script>window.alert=function(){parent.xssed=true;}</script><script>alert(1);</script>'
);
assert.equal(window.xssed, true, 'alert() was called but not detected');
window.xssed = false;
iframe.parentNode.removeChild(iframe);
done();
};
document.body.appendChild(iframe);
});
// Check for isSupported property
QUnit.test('DOMPurify property tests', function (assert) {
assert.equal(typeof DOMPurify.isSupported, 'boolean');
});
// Test with a custom window object
QUnit.test('DOMPurify custom window tests', function (assert) {
assert.strictEqual(typeof DOMPurify(null).version, 'string');
assert.strictEqual(DOMPurify(null).isSupported, false);
assert.strictEqual(DOMPurify(null).sanitize, undefined);
assert.strictEqual(typeof DOMPurify({}).version, 'string');
assert.strictEqual(DOMPurify({}).isSupported, false);
assert.strictEqual(DOMPurify({}).sanitize, undefined);
assert.strictEqual(
typeof DOMPurify({
document: 'not really a document',
Element: window.Element,
}).version,
'string'
);
assert.strictEqual(
DOMPurify({
document: 'not really a document',
Element: window.Element,
}).isSupported,
false
);
assert.strictEqual(
DOMPurify({
document: 'not really a document',
Element: window.Element,
}).sanitize,
undefined
);
assert.strictEqual(
typeof DOMPurify({ document, Element: undefined }).version,
'string'
);
assert.strictEqual(
DOMPurify({ document, Element: undefined }).isSupported,
false
);
assert.strictEqual(
DOMPurify({ document, Element: undefined }).sanitize,
undefined
);
assert.strictEqual(
typeof DOMPurify({ document, Element: window.Element }).version,
'string'
);
assert.strictEqual(
typeof DOMPurify({ document, Element: window.Element }).sanitize,
'function'
);
assert.strictEqual(typeof DOMPurify(window).version, 'string');
assert.strictEqual(typeof DOMPurify(window).sanitize, 'function');
});
// Test to prevent security issues with pre-clobbered DOM
QUnit.test(
'sanitize() should not throw if the original document is clobbered _after_ DOMPurify has been instantiated',
function (assert) {
var evilNode = document.createElement('div');
evilNode.innerHTML =
'<img id="implementation"><img id="createNodeIterator"><img id="importNode"><img id="createElement">';
document.body.appendChild(evilNode);
try {
// tests implementation and createNodeIterator
var resultPlain = DOMPurify.sanitize('123');
// tests importNode
var resultImport = DOMPurify.sanitize('123', {
RETURN_DOM: true,
ADD_ATTR: ['shadowroot'],
});
// tests createElement
var resultBody = DOMPurify.sanitize('123<img id="body">');
} finally {
// clean up before doing the actual assertions, otherwise qunit/jquery/etc might blow up
document.body.removeChild(evilNode);
}
assert.equal(resultPlain, '123');
assert.equal(resultImport.ownerDocument, document);
assert.equal(resultBody, '123<img>');
}
);
// Tests to ensure that a configuration can be set and cleared
QUnit.test(
'ensure that a persistent configuration can be set and cleared',
function (assert) {
var dirty = '<foobar>abc</foobar>';
assert.equal(DOMPurify.sanitize(dirty), 'abc');
DOMPurify.setConfig({ ADD_TAGS: ['foobar'] });
assert.equal(DOMPurify.sanitize(dirty), '<foobar>abc</foobar>');
DOMPurify.clearConfig();
assert.equal(DOMPurify.sanitize(dirty), 'abc');
}
);
// Test to ensure that a hook can add allowed tags / attributes on the fly
QUnit.test(
'ensure that a hook can add allowed tags / attributes on the fly',
function (assert) {
DOMPurify.addHook('uponSanitizeElement', function (node, data) {
if (
node.nodeName &&
node.nodeName.match(/^\w+-\w+$/) &&
!data.allowedTags[data.tagName]
) {
data.allowedTags[data.tagName] = true;
}
});
DOMPurify.addHook('uponSanitizeAttribute', function (node, data) {
if (
data.attrName &&
data.attrName.match(/^\w+-\w+$/) &&
!data.allowedAttributes[data.attrName]
) {
data.allowedAttributes[data.attrName] = true;
}
});
var dirty =
'<p>HE<iframe></iframe><is-custom onload="alert(1)" super-custom="test" />LLO</p>';
var modified =
'<p>HE<is-custom super-custom="test">LLO</is-custom></p>';
assert.equal(DOMPurify.sanitize(dirty), modified);
DOMPurify.removeHooks('uponSanitizeElement');
DOMPurify.removeHooks('uponSanitizeAttribute');
}
);
// Test to ensure that if input[type=file] is badlisted and flagged as an
// attribute not to keep via hookEvent.keepAttr, it should be removed despite
// it being an issue of being able to programmatically add it back in Safari.
QUnit.test(
'ensure that input[type=file] is removed via hookEvent keepAttr',
function (assert) {
DOMPurify.addHook('uponSanitizeAttribute', function (node, data) {
if (
node.nodeName == 'INPUT' &&
node.getAttribute('type') &&
node.getAttribute('type') == 'file'
) {
data.keepAttr = false;
}
});
var dirty = '<input type="file" />';
var modified = '<input>';
if (window.name == 'nodejs') {
assert.equal(DOMPurify.sanitize(dirty), modified);
} else {
assert.expect(0);
}
DOMPurify.removeHooks('uponSanitizeAttribute');
}
);
QUnit.test(
'sanitize() should allow unknown protocols when ALLOW_UNKNOWN_PROTOCOLS is true',
function (assert) {
var dirty =
'<div><a href="spotify:track:12345"><img src="cid:1234567"></a></div>';
assert.equal(
dirty,
DOMPurify.sanitize(dirty, { ALLOW_UNKNOWN_PROTOCOLS: true })
);
}
);
QUnit.test(
'sanitize() should not allow javascript when ALLOW_UNKNOWN_PROTOCOLS is true',
function (assert) {
var dirty =
'<div><a href="javascript:alert(document.title)"><img src="cid:1234567"/></a></div>';
var modified = '<div><a><img src="cid:1234567"></a></div>';
assert.equal(
modified,
DOMPurify.sanitize(dirty, { ALLOW_UNKNOWN_PROTOCOLS: true })
);
}
);
QUnit.test(
'Regression-Test to make sure #166 stays fixed',
function (assert) {
var dirty = '<p onFoo="123">HELLO</p>';
var modified = '<p>HELLO</p>';
assert.equal(
modified,
DOMPurify.sanitize(dirty, { ALLOW_UNKNOWN_PROTOCOLS: true })
);
}
);
// Test 1 to check if the element count in DOMPurify.removed is correct
QUnit.test(
'DOMPurify.removed should contain one element',
function (assert) {
var dirty =
'<svg onload=alert(1)><filter><feGaussianBlur /></filter></svg>';
DOMPurify.sanitize(dirty);
assert.contains(DOMPurify.removed.length, [1, 2]); // IE removes two
}
);
// Test 2 to check if the element count in DOMPurify.removed is correct
QUnit.test(
'DOMPurify.removed should contain two elements',
function (assert) {
var dirty =
'1<script>alert(1)</script><svg onload=alert(1)><filter><feGaussianBlur /></filter></svg>';
DOMPurify.sanitize(dirty);
assert.contains(DOMPurify.removed.length, [2, 3]); // IE removed three
}
);
// Test 3 to check if the element count in DOMPurify.removed is correct
QUnit.test('DOMPurify.removed should be correct', function (assert) {
var dirty = '<img src=x onerror="alert(1)">';
DOMPurify.sanitize(dirty);
assert.equal(DOMPurify.removed.length, 1);
});
// Test 4 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
QUnit.test(
'DOMPurify.removed should be correct in SAFE_FOR_TEMPLATES mode',
function (assert) {
var dirty = '<a>123{{456}}</a>';
DOMPurify.sanitize(dirty, {
WHOLE_DOCUMENT: true,
SAFE_FOR_TEMPLATES: true,
});
assert.equal(DOMPurify.removed.length, 1);
}
);
// Test 5 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
QUnit.test(
'DOMPurify.removed should be correct in SAFE_FOR_TEMPLATES mode',
function (assert) {
var dirty = '<a>123{{456}}<b>456{{789}}</b></a>';
DOMPurify.sanitize(dirty, {
WHOLE_DOCUMENT: true,
SAFE_FOR_TEMPLATES: true,
});
assert.equal(DOMPurify.removed.length, 2);
}
);
// Test 6 to check that DOMPurify.removed is correct in SAFE_FOR_TEMLATES mode
QUnit.test(
'DOMPurify.removed should be correct in SAFE_FOR_TEMPLATES mode',
function (assert) {
var dirty = '<img src=1 width="{{123}}">';
DOMPurify.sanitize(dirty, {
WHOLE_DOCUMENT: true,
SAFE_FOR_TEMPLATES: true,
});
assert.equal(DOMPurify.removed.length, 1);
}
);
// Test 7 to check that DOMPurify.removed is correct
QUnit.test('DOMPurify.removed should be correct', function (assert) {
var dirty = '<option><iframe></select><b><script>alert(1)</script>';
DOMPurify.sanitize(dirty);
assert.equal(DOMPurify.removed.length, 1);
});
// Test 8 to check that DOMPurify.removed is correct if tags are clean
QUnit.test(
'DOMPurify.removed should not contain elements if tags are permitted',
function (assert) {
var dirty = '<a>123</a>';
DOMPurify.sanitize(dirty);
assert.equal(DOMPurify.removed.length, 0);
}
);
// Test 9 to check that DOMPurify.removed is correct if the tags and attributes are clean
QUnit.test(
'DOMPurify.removed should not contain elements if all tags and attrs are permitted',
function (assert) {
var dirty = '<img src=x>';
DOMPurify.sanitize(dirty);
assert.equal(DOMPurify.removed.length, 0);
}
);
// Test 10 to check that DOMPurify.removed does not have false positive elements in SAFE_FOR_TEMLATES mode
QUnit.test(
'DOMPurify.removed should not contain elements for valid data in SAFE_FOR_TEMLATES mode',
function (assert) {
var dirty = '1';
DOMPurify.sanitize(dirty, {
WHOLE_DOCUMENT: true,
SAFE_FOR_TEMPLATES: true,
});
assert.equal(DOMPurify.removed.length, 0);
}
);
// Test 11 to check that DOMPurify.removed does not have false positive elements
QUnit.test(
'DOMPurify.removed should not contain elements for valid data',
function (assert) {
var dirty = '1';
DOMPurify.sanitize(dirty, {
WHOLE_DOCUMENT: true,
});
assert.equal(DOMPurify.removed.length, 0);
}
);
// Tests to make sure that the node scanning feature delivers accurate results on all browsers
QUnit.test(
'DOMPurify should deliver accurate results when sanitizing nodes 1',
function (assert) {
var clean = DOMPurify.sanitize(document.createElement('td'));
assert.equal(clean, '<td></td>');
}
);
QUnit.test(
'DOMPurify should deliver accurate results when sanitizing nodes 2',
function (assert) {
var clean = DOMPurify.sanitize(document.createElement('td'), {
RETURN_DOM: true,
});
assert.equal(clean.outerHTML, '<body><td></td></body>');
}
);
// Test to make sure that URI_safe attributes can be configured too
QUnit.test(
'DOMPurify should allow to define URI safe attributes',
function (assert) {
var clean = DOMPurify.sanitize('<b typeof="bla:h">123</b>', {
ALLOWED_ATTR: ['typeof'],
ADD_URI_SAFE_ATTR: ['typeof'],
});
assert.equal(clean, '<b typeof="bla:h">123</b>');
}
);
// Test to make sure that URI_safe attributes don't persist, see #327
QUnit.test(
'DOMPurify should not persist URI safe attributes',
function (assert) {
var clean = DOMPurify.sanitize('<b typeof="bla:h">123</b>', {
ALLOWED_ATTR: ['typeof'],
ADD_URI_SAFE_ATTR: ['typeof'],
});
var clean = DOMPurify.sanitize('<b typeof="bla:h">123</b>', {
ALLOWED_ATTR: ['typeof'],
});
assert.equal(clean, '<b>123</b>');
}
);
// Test to make sure that URI_safe attributes don't overwrite default, see #366
QUnit.test(
'DOMPurify should not overwrite default URI safe attributes',
function (assert) {
var clean = DOMPurify.sanitize(
'<div poster="x:y" style="color: red">Test</div>',
{ ADD_URI_SAFE_ATTR: ['poster'] }
);
assert.contains(clean, [
'<div style="color: red" poster="x:y">Test</div>',
'<div style="color: red;" poster="x:y">Test</div>',
]);
clean = DOMPurify.sanitize(
'<div poster="x:y" style="color: red">Test</div>'
);
assert.contains(clean, [
'<div style="color: red">Test</div>',
'<div style="color: red;">Test</div>',
]);
}
);
// Tests to make sure that FORCE_BODY pushes elements to document.body (#199)
QUnit.test(
'FORCE_BODY needs to push some elements to document.body',
function (assert) {
var clean = DOMPurify.sanitize('<style>123</style>', {
FORCE_BODY: true,
});
assert.equal(clean, '<style>123</style>');
}
);
QUnit.test(
'FORCE_BODY needs to push some elements to document.body',
function (assert) {
var clean = DOMPurify.sanitize('<script>123</script>', {
FORCE_BODY: true,
ADD_TAGS: ['script'],
});
assert.equal(clean, '<script>123</script>');
}
);
QUnit.test(
'FORCE_BODY needs to push some elements to document.body',
function (assert) {
var clean = DOMPurify.sanitize(' AAAAA', { FORCE_BODY: true });
assert.equal(clean, ' AAAAA');
}
);
QUnit.test(
'Lack of FORCE_BODY still preserves leading whitespace',
function (assert) {
var clean = DOMPurify.sanitize(' <b>AAAAA</b>', { FORCE_BODY: false });
assert.equal(clean, ' <b>AAAAA</b>');
}
);
QUnit.test(
'Lack of FORCE_BODY needs to push some elements to document.head',
function (assert) {
var clean = DOMPurify.sanitize('<style>123</style>', {
FORCE_BODY: false,
});
assert.equal(clean, '');
}
);
// Test to make sure that ALLOW_ARIA_ATTR is working as expected (#198)
QUnit.test('Config-Flag tests: ALLOW_ARIA_ATTR', function (assert) {
assert.contains(
DOMPurify.sanitize('<a aria-abc="foo" href="#">abc</a>', {
ALLOW_ARIA_ATTR: true,
}),
[
'<a aria-abc="foo" href="#">abc</a>',
'<a href="#" aria-abc="foo">abc</a>',
]
);
assert.equal(
DOMPurify.sanitize('<a href="#" aria-aöü="foo">abc</a>', {
ALLOW_ARIA_ATTR: true,
}),
'<a href="#">abc</a>'
);
assert.equal(
DOMPurify.sanitize('<a href="#" aria-abc="foo">abc</a>', {
ALLOW_ARIA_ATTR: false,
}),
'<a href="#">abc</a>'
);
assert.equal(
DOMPurify.sanitize('<a href="#" aria-äöü="foo">abc</a>', {
ALLOW_ARIA_ATTR: false,
}),
'<a href="#">abc</a>'
);
});
QUnit.test('Config-Flag tests: USE_PROFILES', function (assert) {
assert.equal(
DOMPurify.sanitize('<h1>HELLO</h1>', { USE_PROFILES: { html: false } }),
'HELLO'
);
assert.equal(
DOMPurify.sanitize('<h1>HELLO</h1>', { USE_PROFILES: { html: true } }),
'<h1>HELLO</h1>'
);
assert.contains(
DOMPurify.sanitize('<h1>HELLO</h1><math></math>', {
USE_PROFILES: { html: true, mathMl: true },
}),
[
'<h1>HELLO</h1>',
'<h1>HELLO</h1><math></math>',
'<h1>HELLO</h1><math></math>',
]
);
assert.contains(
DOMPurify.sanitize('<h1>HELLO</h1><math><mi></mi></math>', {
USE_PROFILES: { html: true, mathMl: true },
}),
[
'<h1>HELLO</h1>',
'<h1>HELLO</h1><math><mi></mi></math>',
'<h1>HELLO</h1><math></math>',
]
);
assert.contains(
DOMPurify.sanitize('<h1>HELLO</h1><math><mi></mi></math>', {
USE_PROFILES: { html: true, mathMl: true },
FORBID_TAGS: ['mi'],
}),
[
'<h1>HELLO</h1>',
'<h1>HELLO</h1><math></math>',
'<h1>HELLO</h1><math></math>',
]
);
assert.contains(
DOMPurify.sanitize('<h1>HELLO</h1><math class="foo"><mi></mi></math>', {
USE_PROFILES: { html: true, mathMl: true },
FORBID_ATTR: ['class'],
}),
[
'<h1>HELLO</h1>',
'<h1>HELLO</h1><math><mi></mi></math>',
'<h1>HELLO</h1><math></math>',
]
);
assert.equal(
DOMPurify.sanitize('<h1>HELLO</h1>', { USE_PROFILES: { bogus: true } }),
'HELLO'
);
assert.equal(
DOMPurify.sanitize('<h1>HELLO</h1>', { USE_PROFILES: 123 }),
'HELLO'
);
assert.equal(
DOMPurify.sanitize('<h1>HELLO</h1>', { USE_PROFILES: [] }),
'HELLO'
);
assert.contains(
DOMPurify.sanitize('<svg><rect height="50"></rect></svg>', {
USE_PROFILES: { svg: true },
}),
[
'',
'<svg><rect height="50"></rect></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><rect height="50" /></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
]
);
assert.contains(
DOMPurify.sanitize(
'<svg><feBlend in="SourceGraphic" mode="multiply" /></svg>',
{
USE_PROFILES: { svgFilters: true },
ADD_TAGS: ['svg'],
}
),
[
'<svg><feblend in="SourceGraphic" mode="multiply"></feblend></svg>',
'<svg><feblend mode="multiply" in="SourceGraphic"></feblend></svg>',
'<svg><feBlend mode="multiply" in="SourceGraphic"></feBlend></svg>',
'<svg><feBlend mode="multiply" in="SourceGraphic"></feBlend></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><feBlend in="SourceGraphic" mode="multiply" /></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
]
);
assert.contains(
DOMPurify.sanitize(
'<svg><style>.some-class {fill: #fff}</style></svg>',
{
USE_PROFILES: { svg: true },
}
),
[
'',
'<svg><style>.some-class {fill: #fff}</style></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><style>.some-class {fill: #fff}</style></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
]
);
assert.contains(
DOMPurify.sanitize('<svg><text>SEE ME</text></svg>', {
USE_PROFILES: { svg: true },
KEEP_CONTENT: false,
}),
[
'',
'<svg><text>SEE ME</text></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><text>SEE ME</text></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
]
);
assert.equal(
DOMPurify.sanitize('<span>SEE ME</span>', {
USE_PROFILES: { html: true },
KEEP_CONTENT: false,
}),
'<span>SEE ME</span>'
);
assert.equal(
DOMPurify.sanitize('<div></div>', {
USE_PROFILES: { svg: true },
ADD_TAGS: ['div'],
}),
'<div></div>'
);
assert.contains(
DOMPurify.sanitize('<svg keep="me"></svg>', {
USE_PROFILES: { svg: true },
ADD_ATTR: ['keep'],
}),
[
'',
'<svg keep="me"></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" keep="me" />',
]
);
});
QUnit.test('Config-Flag tests: ALLOWED_URI_REGEXP', function (assert) {
var tests = [
{
test: '<img src="https://i.imgur.com/hkfpOUu.gifv">',
expected: '<img src="https://i.imgur.com/hkfpOUu.gifv">',
},
{
test: '<img src="http://i.imgur.com/WScAnHr.jpg">',
expected: '<img src="http://i.imgur.com/WScAnHr.jpg">',
},
{
test:
'<img src="blob:https://localhost:3000/c4ea3ec6-9f22-4d08-af6f-d79e78a0a7a7">',
expected: '<img>',
},
{
test: '<a href="mailto:demo@example.com">demo</a>',
expected: '<a>demo</a>',
},
];
tests.forEach(function (test) {
var str = DOMPurify.sanitize(test.test, {
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
});
assert.equal(str, test.expected);
});
});
QUnit.test('Ensure ALLOWED_URI_REGEXP is not cached', function(assert) {
const
dirty = '<img src="https://different.com">',
expected = '<img src="https://different.com">';
assert.equal(DOMPurify.sanitize(dirty), expected);
// sanitize with a custom URI regexp
assert.equal(DOMPurify.sanitize('<img src="https://test.com">', {
ALLOWED_URI_REGEXP: /test\.com/i
}), '<img src="https://test.com">');
// ensure that the previous regexp does not affect future sanitize calls
assert.equal(DOMPurify.sanitize(dirty), expected);
});
QUnit.test(
'Avoid freeze when using tables and ALLOW_TAGS',
function (assert) {
var clean = DOMPurify.sanitize('<table><tr><td></td></tr></table>', {
ALLOW_TAGS: ['table', 'tr', 'td'],
});
assert.equal(clean, '<table><tbody><tr><td></td></tr></tbody></table>');
}
);
QUnit.test(
'Avoid XSS with ALLOW_TAGS permitting noembed, noscript',
function (assert) {
var clean = DOMPurify.sanitize(
"a<noembed><p id='</noembed><img src=x onerror=alert(1)>'></p></noembed>",
{ ADD_TAGS: ['noembed'] }
);
assert.contains(clean, [
'a<noembed><p id=\'</noembed><img src="x">\'&gt;<p></p>',
'a',
'a<noembed>&lt;p id=\'</noembed><img src="x">\'&gt;<p></p>',
'a<noembed><p id="&lt;/noembed&gt;&lt;img src=x onerror=alert(1)&gt;"></p></noembed>',
'a<noembed></noembed>',
'a<img src="x">\'&gt;<p></p>',
]);
}
);
QUnit.test(
'Avoid mXSS in Chrome 77 and above using SVG',
function (assert) {
var clean = DOMPurify.sanitize('<svg></p><style><g title="</style>');
assert.contains(clean, [
'',
'<svg></svg><p></p><style><g title="</style>',
'<p></p><style><g title="</style>',
'<svg></svg><p></p>',
'<svg><style></style></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><style /></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><style /></svg></svg>',
]);
}
);
QUnit.test(
'Avoid mXSS in Chrome 77 and above using HTML',
function (assert) {
var clean = DOMPurify.sanitize('<svg></p><title><a href="</title>qqq');
assert.contains(clean, [
'',
'<svg></svg><p></p><title>&lt;a href="</title>qqq<img src="">"&gt;',
'<svg></svg><p></p><title>&lt;a href="</title>qqq',
'<p></p><title>&lt;a href="</title>qqq',
'<svg></svg><p></p>qqq',
'<svg><title></title></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><title /></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><title /></svg></svg>',
]);
}
);
QUnit.test(
'Test for correct return value when RETURN_TRUSTED_TYPE is true',
function (assert) {
var clean = DOMPurify.sanitize('<b>hello goodbye</b>', {
RETURN_TRUSTED_TYPE: true,
});
var type = typeof clean;
assert.contains(type, ['TrustedHTML', 'string', 'object']);
}
);
QUnit.test(
'Test for correct return value when RETURN_TRUSTED_TYPE is false',
function (assert) {
var clean = DOMPurify.sanitize('<b>hello goodbye</b>', {
RETURN_TRUSTED_TYPE: false,
});
var type = typeof clean;
assert.equal(type, 'string');
}
);
QUnit.test(
'Test for correct return value when RETURN_TRUSTED_TYPE is not set',
function (assert) {
var clean = DOMPurify.sanitize('<b>hello goodbye</b>');
var type = typeof clean;
assert.equal(type, 'string');
}
);
QUnit.test(
'Test for DoS coming from table sanitization 1/2 See #365',
function (assert) {
var config = { FORBID_TAGS: ['tbody'] };
var clean = DOMPurify.sanitize(
'<table><tbody><tr><td>test</td></tr></tbody></table>',
config
);
assert.equal(
clean,
'<table><tbody><tr><td>test</td></tr></tbody></table>'
);
}
);
QUnit.test(
'Test for DoS coming from table sanitization 2/2 See #365',
function (assert) {
var config = {
ALLOWED_TAGS: [
'b',
'strong',
'i',
'italic',
'div',
'p',
'span',
'ul',
'li',
'ol',
'a',
'img',
'br',
'tr',
'td',
'th',
'table',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
],
ALLOW_DATA_ATTR: false,
ALLOWED_ATTR: ['src', 'class', 'target', 'href'],
};
var clean = DOMPurify.sanitize(
'<table><colgroup><col></col></colgroup><tbody><tr><td >test</td></tr></tbody></table>',
config
);
assert.equal(
clean,
'<table><tbody><tr><td>test</td></tr></tbody></table>'
);
}
);
QUnit.test(
'Test for less aggressive mXSS handling, See #369',
function (assert) {
var config = {
FORBID_TAGS: ['svg', 'math'],
};
var clean = DOMPurify.sanitize(
'<b data-test="<span>content</span>"></b>',
config
);
assert.contains(clean, [
'<b data-test="<span>content</span>"></b>',
'<b data-test="&lt;span&gt;content&lt;/span&gt;"></b>',
]);
}
);
QUnit.test(
'Test against mXSS using text integration points and removal 1/2',
function (assert) {
var config = {
FORBID_TAGS: ['mi'],
};
var clean = DOMPurify.sanitize(
'<math><mi><b><style><b title="</style><iframe onload&#x3d;alert(1)<!--"></style>',
config
);
assert.contains(clean, [
'<math><b><style><b title="</style></b></math>',
'<math></math>',
'',
]);
}
);
QUnit.test(
'Test against mXSS using text integration points and removal 2/2',
function (assert) {
var config = {
ADD_TAGS: ['xmp'],
};
var clean = DOMPurify.sanitize(
"x<noframes><svg><b><xmp><b title='</xmp><img>",
config
);
assert.contains(clean, ['x']);
}
);
QUnit.test(
'Test against insecure behavior in jQUery v3.0 and newer 1/2',
function (assert) {
var config = {};
var clean = DOMPurify.sanitize(
'<img x="/><img src=x onerror=alert(1)>" y="<x">',
config
);
assert.contains(clean, [
'<img y="<x" x="/><img src=x onerror=alert(1)>">', // jsdom
'<img y="<x">',
'<img y="&lt;x">',
'<img y="<x">',
]);
}
);
QUnit.test(
'Test against insecure behavior in jQUery v3.0 and newer 2/2',
function (assert) {
var config = {};
var clean = DOMPurify.sanitize(
"a<noscript><p id='><noscript /><img src=x onerror=alert(1)>'></noscript>",
config
);
assert.contains(clean, [
"a<noscript>&lt;p id='>&lt;noscript />&lt;img src=x onerror=alert(1)>'></noscript>", // jsdom
'a<noscript><p></p></noscript>',
'a<p></p>',
'a',
]);
}
);
QUnit.test(
'Test against data URIs in anchors without proper config flag',
function (assert) {
var clean = DOMPurify.sanitize(
'<a href="data:image/gif;base64,123">icon.gif</a>'
);
assert.equal(clean, '<a>icon.gif</a>');
}
);
QUnit.test(
'Test against data URIs in anchors using proper config flag',
function (assert) {
var clean = DOMPurify.sanitize(
'<a href="data:image/gif;base64,123">icon.gif</a>',
{
ADD_DATA_URI_TAGS: ['a', 'b'],
}
);
assert.equal(clean, '<a href="data:image/gif;base64,123">icon.gif</a>');
}
);
QUnit.test(
'Test against Unicode tag names and proper removal',
function (assert) {
var clean = DOMPurify.sanitize('<svg><blocKquote>foo</blocKquote>');
assert.contains(clean, [
'<svg></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
]);
var clean = DOMPurify.sanitize('<svg><blocKquote>foo</blocKquote>');
assert.contains(clean, [
'<svg></svg><blockquote>foo</blockquote>',
'<svg><blockquote>foo</blockquote></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" /><blockquote>foo</blockquote>',
]);
}
);
QUnit.test('Test if namespaces are properly enforced', function (assert) {
var tests = [
{
test:
'<svg><desc><canvas></canvas><textarea></textarea></desc></svg>',
expected: [
'<svg><desc></desc></svg>',
'<svg xmlns="http://www.w3.org/2000/svg"><desc></desc></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
],
},
{
test: '<svg><canvas></canvas><textarea></textarea></svg>',
expected: [
'<svg></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
],
},
{
test: '<math><canvas></canvas><textarea></textarea></math>',
expected: ['<math></math>'],
},
{
test: '<math><mi><canvas></canvas><textarea></textarea></mi></math>',
expected: [
'<math><mi><canvas></canvas><textarea></textarea></mi></math>',
'<math></math>',
],
},
{
test: '<svg><math></math><title><math></math></title></svg>',
expected: [
'<svg><title></title></svg>',
'<svg xmlns="http://www.w3.org/2000/svg" />',
'<svg xmlns="http://www.w3.org/2000/svg"><title></title></svg>',
],
},
{
test: '<math><svg></svg><mi><svg></svg></mi></math>',
expected: [
'<math><mi><svg></svg></mi></math>',
'<math><mi><svg xmlns="http://www.w3.org/2000/svg" /></mi></math>',
'<math></math>',
],
},
{
test: '<form><math><mi><mglyph></form><form>',
expected: [
'<form><math><mi><mglyph></mglyph></mi></math></form>',
'<form><math></math></form>',
],
},
];
tests.forEach(function (test) {
var clean = DOMPurify.sanitize(test.test);
assert.contains(clean, test.expected);
});
});
QUnit.test('Config-Flag tests: NAMESPACE', function (assert) {
var tests = [
{
test: '<polyline points="0 0"></polyline>',
config: { NAMESPACE: 'http://www.w3.org/2000/svg' },
expected: [
'<polyline points="0 0"></polyline>',
'<polyline xmlns="http://www.w3.org/2000/svg" points="0 0"/>',
'<polyline xmlns="http://www.w3.org/2000/svg" points="0,0" />',
'',
],
},
{
test: '<polyline points="0 0"></polyline>',
config: { NAMESPACE: 'http://www.w3.org/1999/xhtml' },
expected: [''],
},
{
test: '<mi></mi>',
config: { NAMESPACE: 'http://www.w3.org/1998/Math/MathML' },
expected: [
'<mi></mi>',
'<mi xmlns="http://www.w3.org/1998/Math/MathML"></mi>',
'<mi xmlns="http://www.w3.org/1998/Math/MathML"/>',
'<mi xmlns="http://www.w3.org/1998/Math/MathML" />',
'',
],
},
{
test: '<polyline points="0 0"></polyline>',
config: { NAMESPACE: 'http://www.w3.org/1998/Math/MathML' },
expected: [''],
},
{
test: '<mi></mi>',
config: { NAMESPACE: 'http://www.w3.org/1999/xhtml' },
expected: [''],
},
];
tests.forEach(function (test) {
var clean = DOMPurify.sanitize(test.test, test.config);
assert.contains(clean, test.expected);
});
});
QUnit.test('Config-Flag tests: ALLOWED_NAMESPACES', function (assert) {
const tests = [
// Test when ALLOWED_NAMESPACES is not set, result is empty for XML with custom namespace
{
test:
'<library xmlns="http://www.ibm.com/library"><name>Library 1</name></library>',
config: {
ALLOWED_TAGS: ['#text', 'library', 'name'],
KEEP_CONTENT: false,
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
},
expected: '',
},
// Test with one custom namespace at the root (ie. all sub-nodes will inherit that namespace)
{
test:
'<library xmlns="http://www.ibm.com/library"><name>Library 1</name><dirty onload="alert()" /></library>',
config: {
ALLOWED_NAMESPACES: ['http://www.ibm.com/library'],
ALLOWED_TAGS: ['#text', 'library', 'name'],
KEEP_CONTENT: false,
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
},
expected:
'<library xmlns="http://www.ibm.com/library"><name>Library 1</name></library>',
},
// Test with one custom namespace at sub-node (root will default to HTML_NAMESPACE and should be kept)
{
test:
'<city><library xmlns="http://www.ibm.com/library"><name>Library 1</name><dirty onload="alert()" /></library></city>',
config: {
ALLOWED_NAMESPACES: ['http://www.w3.org/1999/xhtml', 'http://www.ibm.com/library'],
ALLOWED_TAGS: ['#text', 'city', 'library', 'name'],
KEEP_CONTENT: false,
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
},
expected:
'<city xmlns="http://www.w3.org/1999/xhtml"><library xmlns="http://www.ibm.com/library"><name>Library 1</name></library></city>',
},
// Test removal of namespaces not listed in ALLOWED_NAMESPACES when input has multiple namespaces
{
test:
'<library xmlns="http://www.ibm.com/library" xmlns:bk="urn:loc.gov:books"><bk:name>Library 1</bk:name><dirty onload="alert()" /></library>',
config: {
ALLOWED_NAMESPACES: ['http://www.ibm.com/library'],
ALLOWED_TAGS: ['library', 'bk:name'],
KEEP_CONTENT: false,
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
},
expected: ['<library xmlns="http://www.ibm.com/library"/>', '<library xmlns="http://www.ibm.com/library" />']
},
// Test with multiple custom namespaces and prefixes in input
{
test:
'<library xmlns="http://www.ibm.com/library" xmlns:bk="urn:loc.gov:books" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><bk:name>Library 1<m:properties>Other Properties</m:properties></bk:name><dirty onload="alert()" /></library>',
config: {
ALLOWED_NAMESPACES: [
'http://www.ibm.com/library',
'urn:loc.gov:books',
'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata',
],
ALLOWED_TAGS: ['#text', 'library', 'bk:name', 'm:properties'],
KEEP_CONTENT: false,
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
},
expected:
'<library xmlns="http://www.ibm.com/library"><bk:name xmlns:bk="urn:loc.gov:books">Library 1<m:properties xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">Other Properties</m:properties></bk:name></library>',
},
// Test removal of elements mentioned in FORBID_TAGS even if their namespaces are allow-listed
{
test:
'<library xmlns="http://www.ibm.com/library" xmlns:bk="urn:loc.gov:books" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><bk:name>Library 1<m:properties>Other Properties</m:properties></bk:name><dirty onload="alert()" /></library>',
config: {
ADD_TAGS: ['library', 'bk:name'],
ALLOWED_NAMESPACES: [
'http://www.ibm.com/library',
'urn:loc.gov:books',
'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata',
],
FORBID_TAGS: ['m:properties'],
KEEP_CONTENT: false,
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
},
expected:
'<library xmlns="http://www.ibm.com/library"><bk:name xmlns:bk="urn:loc.gov:books">Library 1</bk:name></library>',
},
];
tests.forEach(function (test) {
assert.contains(DOMPurify.sanitize(test.test, test.config), test.expected);
});
});
QUnit.test('Config-Flag tests: PARSER_MEDIA_TYPE', function (assert) {
const tests = [
{
test: '<A href="#">invalid</A><a TITLE="title" href="#">valid</a>',
expected: {
'': [
'<a href="#">invalid</a><a href="#" title="title">valid</a>',
'<a href="#">invalid</a><a title="title" href="#">valid</a>',
],
'Application/xhtml+xml': [
'<a href="#">invalid</a><a href="#" title="title">valid</a>',
'<a href="#">invalid</a><a title="title" href="#">valid</a>',
],
'application/xml': [
'<a href="#">invalid</a><a href="#" title="title">valid</a>',
'<a href="#">invalid</a><a title="title" href="#">valid</a>',
],
'application/xhtml+xml': [
'<a href="#">invalid</a><a href="#" title="title">valid</a>',
'invalid<a xmlns="http://www.w3.org/1999/xhtml" href="#">valid</a>',
'invalid<a xmlns="http://www.w3.org/1999/xhtml" href="#" TITLE="title">valid</a>',
],
'text/html': [
'<a href="#">invalid</a><a href="#" title="title">valid</a>',
'<a href="#">invalid</a><a title="title" href="#">valid</a>',
],
'text/xml': [
'<a href="#">invalid</a><a href="#" title="title">valid</a>',
'<a href="#">invalid</a><a title="title" href="#">valid</a>',
],
},
},
{
config: {
WHOLE_DOCUMENT: true,
},
test: '<A href="#">invalid</A><a TITLE="title" href="#">valid</a>',
expected: {
'text/html': [
'<html><head></head><body><a href="#">invalid</a><a href="#" title="title">valid</a></body></html>',
'<html><head></head><body><a href="#">invalid</a><a title="title" href="#">valid</a></body></html>',
],
'application/xhtml+xml': [
'<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>invalid<a href="#">valid</a></body></html>',
'<html xmlns="http://www.w3.org/1999/xhtml"><head /><body>invalid<a href="#">valid</a></body></html>',
'<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>invalid<a href="#" TITLE="title">valid</a></body></html>',
],
},
},
];
tests.forEach(function (test) {
Object.keys(test.expected).forEach(function (type) {
var config = test.config || {};
config.PARSER_MEDIA_TYPE = type;
var clean = DOMPurify.sanitize(test.test, config);
assert.contains(clean, test.expected[type]);
});
});
});
QUnit.test(
'Config-Flag tests: PARSER_MEDIA_TYPE + ALLOWED_TAGS/ALLOWED_ATTR',
function (assert) {
assert.contains(
DOMPurify.sanitize(
'<a href="#">abc</a><CustomTag customattr="bar" CustomAttr="foo"/>',
{
PARSER_MEDIA_TYPE: 'application/xhtml+xml',
ALLOWED_TAGS: ['a', 'CustomTag'],
ALLOWED_ATTR: ['href', 'CustomAttr'],
}
),
[
'<a xmlns="http://www.w3.org/1999/xhtml" href="#">abc</a>' +
'<CustomTag xmlns="http://www.w3.org/1999/xhtml" CustomAttr="foo"></CustomTag>',
'<a xmlns="http://www.w3.org/1999/xhtml" href="#">abc</a><CustomTag xmlns="http://www.w3.org/1999/xhtml" CustomAttr="foo" customattr="foo"></CustomTag>',
]
);
}
);
QUnit.test('Test invalid xml', function (assert) {
var tests = [
{
test: '',
config: { NAMESPACE: 'http://www.w3.org/2000/svg' },
expected: [''],
},
{
test: '<!-->',
config: { NAMESPACE: 'http://www.w3.org/2000/svg' },
expected: ['', '<!-->'],
},
{
test: '',
config: { NAMESPACE: 'http://www.w3.org/1998/Math/MathML' },
expected: [''],
},
{
test: '<!-->',
config: { NAMESPACE: 'http://www.w3.org/1998/Math/MathML' },
expected: ['', '<!-->'],
},
{
test: '',
config: { NAMESPACE: 'http://www.w3.org/1999/xhtml' },
expected: [''],
},
{
test: '',
config: {},
expected: [''],
},
{
test: '<!-->',
config: { NAMESPACE: 'http://www.w3.org/1999/xhtml' },
expected: ['', '<!-->'],
},
{
test: '<!-->',
config: {},
expected: ['', '<!-->'],
},
];
tests.forEach(function (test) {
var clean = DOMPurify.sanitize(test.test, test.config);
assert.contains(clean, test.expected);
});
});
QUnit.test(
'Test namespace default to html after other namespace been used',
function (assert) {
var tests = [
{
test: '<br>',
config: { NAMESPACE: 'http://www.w3.org/2000/svg' },
expected: ['', '<br>'],
},
{
test: '<br>',
config: {},
expected: ['<br>'],
},
];
tests.forEach(function (test) {
var clean = DOMPurify.sanitize(test.test, test.config);
assert.contains(clean, test.expected);
});
}
);
QUnit.test('Test non-html input after empty input', function (assert) {
var tests = [
{
test: '',
config: { NAMESPACE: 'http://www.w3.org/2000/svg' },
expected: [''],
},
{
test: '<polyline points="0 0"></polyline>',
config: { NAMESPACE: 'http://www.w3.org/2000/svg' },
expected: [
'<polyline points="0 0"></polyline>',
'<polyline xmlns="http://www.w3.org/2000/svg" points="0 0"/>',
'<polyline xmlns="http://www.w3.org/2000/svg" points="0,0" />',
'',
],
},
];
tests.forEach(function (test) {
var clean = DOMPurify.sanitize(test.test, test.config);
assert.contains(clean, test.expected);
});
});
QUnit.test('removeHook returns hook function', function (assert) {
const entryPoint = 'afterSanitizeAttributes';
const dirty = '<div class="hello"></div>';
const expected = '<div class="world"></div>';
DOMPurify.addHook(entryPoint, function (node) {
return node.setAttribute('class', 'world');
});
assert.equal(DOMPurify.sanitize(dirty), expected);
// remove hook and keep it
const hookFunction = DOMPurify.removeHook(entryPoint);
assert.equal(DOMPurify.sanitize(dirty), dirty);
// set the same hook
DOMPurify.addHook(entryPoint, hookFunction);
assert.equal(DOMPurify.sanitize(dirty), expected);
// cleanup hook
DOMPurify.removeHook(entryPoint);
});
QUnit.test('removeHook allows specifying the hook to remove', function (assert) {
const entryPoint = 'afterSanitizeAttributes';
const dirty = '<div class="original"></div>';
const expected = '<div class="original first third"></div>';
const firstHook = function (node) {
node.classList.add('first');
};
const secondHook = function (node) {
node.classList.add('second');
};
const thirdHook = function (node) {
node.classList.add('third');
};
DOMPurify.addHook(entryPoint, firstHook);
DOMPurify.addHook(entryPoint, secondHook);
DOMPurify.addHook(entryPoint, thirdHook);
// removes the specified hook
assert.strictEqual(DOMPurify.removeHook(entryPoint, secondHook), secondHook);
// can’t remove it again
assert.strictEqual(DOMPurify.removeHook(entryPoint, secondHook), undefined);
// removed hook isn’t used during sanitize
assert.strictEqual(DOMPurify.sanitize(dirty), expected);
// cleanup hooks
DOMPurify.removeHook(entryPoint, firstHook);
DOMPurify.removeHook(entryPoint, thirdHook);
});
QUnit.test('Test proper removal of annotation-xml w. custom elements', function (assert) {
const dirty = '<svg><annotation-xml><foreignobject><style><!--</style><p id="--><img src=\'x\' onerror=\'alert(1)\'>">';
const config = {
CUSTOM_ELEMENT_HANDLING: { tagNameCheck: /.*/ },
FORBID_CONTENTS: [""]
};
const expected = '<svg></svg>';
let clean = DOMPurify.sanitize(dirty, config);
assert.contains(clean, expected);
});
QUnit.test('Test proper handling of attributes with RETURN_DOM', function (assert) {
const dirty = '<body onload="alert(1)">&lt;a<!-- <f --></body>';
const config = {
RETURN_DOM: true
};
const expected = '<body>&lt;a</body>';
let clean = DOMPurify.sanitize(dirty, config);
let iframe = document.createElement('iframe')
iframe.srcdoc = `<html><head></head>${clean.outerHTML}</html>`
document.body.appendChild(iframe); // alert test
assert.contains(clean.outerHTML, expected);
});
QUnit.test('Test proper handling of data-attribiutes in XML modes', function (assert) {
const dirty = '<svg width="800" height="600" xmlns="http://www.w3.org/2000/svg"><a xmlns:data-slonser="http://www.w3.org/1999/xlink" data-slonser:href="javascript:alert(1)"><text x="20" y="35">Click me!</text></a></svg>';
const config = {
PARSER_MEDIA_TYPE: 'application/xhtml+xml'
};
const expected = [
'<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"600\" width=\"800\"><a><text y=\"35\" x=\"20\">Click me!</text></a></svg>',
'<svg height=\"600\" width=\"800\" xmlns=\"http://www.w3.org/2000/svg\"><a><text y=\"35\" x=\"20\">Click me!</text></a></svg>'
];
let clean = DOMPurify.sanitize(dirty, config);
assert.contains(clean, expected);
});
};
});