import {afterEach, describe, expect, test} from 'vitest'; import {DOM} from './dom.ts'; function sanitizeToHTML(input: string): string { const container = document.createElement('div'); container.append(DOM.sanitize(input)); return container.innerHTML; } describe('DOM', () => { describe('sanitize', () => { // A named form control shadows the same-named property of its own form element in a browser, so markup can // choose what `form.remove` or `form.localName` resolve to. jsdom does not implement that // (`[LegacyOverrideBuiltIns]`), so the emulation below is what makes the tests using it fail against a // sanitizer that reads those off the element rather than off `Element.prototype`. const clobberedProperties: string[] = []; function clobberFormProperty(property: string) { clobberedProperties.push(property); const inherited = Object.getOwnPropertyDescriptor(Element.prototype, property); Object.defineProperty(HTMLFormElement.prototype, property, { configurable: true, get(this: HTMLFormElement) { const named = Element.prototype.querySelectorAll.call(this, `[name="${property}"]`); if (named.length === 0) return inherited.get ? inherited.get.call(this) : inherited.value; return named.length === 1 ? named[0] : named; } }); } afterEach(() => { for (const property of clobberedProperties) delete HTMLFormElement.prototype[property]; clobberedProperties.length = 0; }); test('should not fail on empty string', () => { const input = ''; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove script tags', () => { const input = ''; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove script tags from nested elements', () => { const input = '
'; const output = sanitizeToHTML(input); expect(output).toBe('
'); }); test('should remove potentially dangerous attributes', () => { const input = 'click me'; const output = sanitizeToHTML(input); expect(output).toBe('click me'); }); test('should remove potentially dangerous attributes from img', () => { const input = ''; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove potentially dangerous attributes from nested elements', () => { const input = '
click me
'; const output = sanitizeToHTML(input); expect(output).toBe('
click me
'); }); test('should remove multiple consecutive dangerous attributes', () => { const input = '
x
'; const output = sanitizeToHTML(input); expect(output).not.toContain('onload'); expect(output).not.toContain('ontoggle'); }); test('should remove iframe tags', () => { const input = ''; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove iframe tags from nested elements', () => { const input = '
'; const output = sanitizeToHTML(input); expect(output).toBe('
'); }); test('should remove srcdoc attributes', () => { const input = 'alert(1)\'>x'; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove dangerous attributes that follow a removed attribute', () => { const input = 'click me'; const output = sanitizeToHTML(input); expect(output).toBe('click me'); }); test('should not let mutated markup reintroduce dangerous attributes', () => { const input = '
'; const output = sanitizeToHTML(input); expect(output).toBe('x'); }); test('should remove form tags that could phish for credentials', () => { const input = ''; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove template tags whose contents are never reachable for cleaning', () => { const input = 'x'; const output = sanitizeToHTML(input); expect(output).toBe('x'); }); test('should remove svg and math tags whose parsing rules differ from html', () => { const input = 'click mex'; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove the style attribute that could cover the page with an overlay', () => { const input = '
x
'; const output = sanitizeToHTML(input); expect(output).toBe('
x
'); }); test('should remove javascript urls hidden behind whitespace and control characters', () => { const input = 'click me'; const output = sanitizeToHTML(input); expect(output).toBe('click me'); }); test('should keep aria attributes', () => { const input = 'MapLibre'; const output = sanitizeToHTML(input); expect(output).toBe('MapLibre'); }); test('should remove urls that cannot be parsed', () => { const input = 'click me'; const output = sanitizeToHTML(input); expect(output).toBe('click me'); }); test('should remove data urls', () => { const input = ''; const output = sanitizeToHTML(input); expect(output).toBe(''); }); test('should remove a form whose named control shadows remove', () => { clobberFormProperty('remove'); const input = '
'; expect(sanitizeToHTML(input)).toBe(''); }); test('should remove a form whose named control shadows localName', () => { clobberFormProperty('localName'); const input = '
'; expect(sanitizeToHTML(input)).toBe(''); }); test('should remove a form whose named controls shadow the whole traversal', () => { for (const property of ['remove', 'localName', 'namespaceURI', 'children', 'attributes', 'querySelectorAll']) { clobberFormProperty(property); } const input = '
'; expect(sanitizeToHTML(input)).toBe('
'); }); }); });