diff options
Diffstat (limited to 'spec/frontend/lib/dompurify_spec.js')
-rw-r--r-- | spec/frontend/lib/dompurify_spec.js | 98 |
1 files changed, 98 insertions, 0 deletions
diff --git a/spec/frontend/lib/dompurify_spec.js b/spec/frontend/lib/dompurify_spec.js new file mode 100644 index 00000000000..ee1971a4931 --- /dev/null +++ b/spec/frontend/lib/dompurify_spec.js @@ -0,0 +1,98 @@ +import { sanitize } from '~/lib/dompurify'; + +// GDK +const rootGon = { + sprite_file_icons: '/assets/icons-123a.svg', + sprite_icons: '/assets/icons-456b.svg', +}; + +// Production +const absoluteGon = { + sprite_file_icons: `${window.location.protocol}//${window.location.hostname}/assets/icons-123a.svg`, + sprite_icons: `${window.location.protocol}//${window.location.hostname}/assets/icons-456b.svg`, +}; + +const expectedSanitized = '<svg><use></use></svg>'; + +const safeUrls = { + root: Object.values(rootGon).map(url => `${url}#ellipsis_h`), + absolute: Object.values(absoluteGon).map(url => `${url}#ellipsis_h`), +}; + +const unsafeUrls = [ + '/an/evil/url', + '../../../evil/url', + 'https://evil.url/assets/icons-123a.svg', + 'https://evil.url/assets/icons-456b.svg', + `https://evil.url/${rootGon.sprite_icons}`, + `https://evil.url/${rootGon.sprite_file_icons}`, + `https://evil.url/${absoluteGon.sprite_icons}`, + `https://evil.url/${absoluteGon.sprite_file_icons}`, +]; + +describe('~/lib/dompurify', () => { + let originalGon; + + it('uses local configuration when given', () => { + // As dompurify uses a "Persistent Configuration", it might + // ignore config, this check verifies we respect + // https://github.com/cure53/DOMPurify#persistent-configuration + expect(sanitize('<br>', { ALLOWED_TAGS: [] })).toBe(''); + expect(sanitize('<strong></strong>', { ALLOWED_TAGS: [] })).toBe(''); + }); + + describe.each` + type | gon + ${'root'} | ${rootGon} + ${'absolute'} | ${absoluteGon} + `('when gon contains $type icon urls', ({ type, gon }) => { + beforeAll(() => { + originalGon = window.gon; + window.gon = gon; + }); + + afterAll(() => { + window.gon = originalGon; + }); + + it('allows no href attrs', () => { + const htmlHref = `<svg><use></use></svg>`; + expect(sanitize(htmlHref)).toBe(htmlHref); + }); + + it.each(safeUrls[type])('allows safe URL %s', url => { + const htmlHref = `<svg><use href="${url}"></use></svg>`; + expect(sanitize(htmlHref)).toBe(htmlHref); + + const htmlXlink = `<svg><use xlink:href="${url}"></use></svg>`; + expect(sanitize(htmlXlink)).toBe(htmlXlink); + }); + + it.each(unsafeUrls)('sanitizes unsafe URL %s', url => { + const htmlHref = `<svg><use href="${url}"></use></svg>`; + const htmlXlink = `<svg><use xlink:href="${url}"></use></svg>`; + + expect(sanitize(htmlHref)).toBe(expectedSanitized); + expect(sanitize(htmlXlink)).toBe(expectedSanitized); + }); + }); + + describe('when gon does not contain icon urls', () => { + beforeAll(() => { + originalGon = window.gon; + window.gon = {}; + }); + + afterAll(() => { + window.gon = originalGon; + }); + + it.each([...safeUrls.root, ...safeUrls.absolute, ...unsafeUrls])('sanitizes URL %s', url => { + const htmlHref = `<svg><use href="${url}"></use></svg>`; + const htmlXlink = `<svg><use xlink:href="${url}"></use></svg>`; + + expect(sanitize(htmlHref)).toBe(expectedSanitized); + expect(sanitize(htmlXlink)).toBe(expectedSanitized); + }); + }); +}); |