From 247a6b9d76634fadf7720e74a8e4a004b8a38e5f Mon Sep 17 00:00:00 2001 From: Julien Ramboz Date: Fri, 3 May 2019 09:46:29 -0700 Subject: [PATCH] feat(html pipe): Allow custom elements and attributes in markdown Let the sanitization step accept custom elements and attributes added to the markdown fix #253 --- src/html/sanitize.js | 48 +++++++++++++++++++++++++++++++++++- src/utils/heading-handler.js | 2 +- test/testEmbedHandler.js | 4 +-- test/testHTML.js | 2 +- test/testHTMLFromMarkdown.js | 32 +++++++++++++++++------- 5 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/html/sanitize.js b/src/html/sanitize.js index baac49e74..b32c9d60c 100644 --- a/src/html/sanitize.js +++ b/src/html/sanitize.js @@ -13,15 +13,61 @@ const createDOMPurify = require('dompurify'); const { JSDOM } = require('jsdom'); const helixSanitizationConfig = { - ADD_TAGS: ['esi:include', 'esi:remove'], + // Allowing all ESI tags, see: https://www.w3.org/TR/esi-lang + ADD_TAGS: [ + 'esi:try', + 'esi:attempt', + 'esi:except', + + 'esi:choose', + 'esi:when', + 'esi:otherwise', + + 'esi:include', + 'esi:inline', + 'esi:remove', + + 'esi:vars', + 'esi:comment', + ], RETURN_DOM: true, }; +const CUSTOM_NAME_REGEX = /^\w+-\w+$/; + +/** + * Allow custom elements to be retained by the sanitization. + * + * @param {Object} DOMPurify the DOMPurify instance + */ +function allowCustomElements(DOMPurify) { + DOMPurify.addHook('uponSanitizeElement', (node, data) => { + if (node.nodeName && node.nodeName.match(CUSTOM_NAME_REGEX)) { + data.allowedTags[data.tagName] = true; // eslint-disable-line no-param-reassign + } + }); +} + +/** + * Allow custom attributes to be retained by the sanitization. + * + * @param {Object} DOMPurify the DOMPurify instance + */ +function allowCustomAttributes(DOMPurify) { + DOMPurify.addHook('uponSanitizeAttribute', (node, data) => { + if (data.attrName && data.attrName.match(CUSTOM_NAME_REGEX)) { + data.allowedAttributes[data.attrName] = true; // eslint-disable-line no-param-reassign + } + }); +} + function sanitize({ content }, { logger }) { logger.log('debug', 'Sanitizing content body to avoid XSS injections.'); const globalContext = (new JSDOM('')).window; const DOMPurify = createDOMPurify(globalContext); + allowCustomElements(DOMPurify); + allowCustomAttributes(DOMPurify); const sanitizedBody = DOMPurify.sanitize(content.document.body, helixSanitizationConfig); return { content: { diff --git a/src/utils/heading-handler.js b/src/utils/heading-handler.js index 5c3b5b91b..fb0f30bf7 100644 --- a/src/utils/heading-handler.js +++ b/src/utils/heading-handler.js @@ -45,7 +45,7 @@ class HeadingHandler { // Inject the id after transformation const n = Object.assign({}, node); const el = fallback(h, n); - el.properties.id = el.properties.id || `user-content-${headingIdentifier}`; + el.properties.id = el.properties.id || headingIdentifier; return el; }; } diff --git a/test/testEmbedHandler.js b/test/testEmbedHandler.js index 6f1c413f7..93bde3833 100644 --- a/test/testEmbedHandler.js +++ b/test/testEmbedHandler.js @@ -129,7 +129,7 @@ https://www.youtube.com/watch?v=KOxbO0EI4MA Here comes an embed.

-

Easy!

+

Easy!

`).window.document.body, ); }); @@ -167,7 +167,7 @@ Here comes an embed. Here comes an embed.

-

Easy!

+

Easy!

`).window.document.body, ); }); diff --git a/test/testHTML.js b/test/testHTML.js index 8caa8bade..7f53fa51a 100644 --- a/test/testHTML.js +++ b/test/testHTML.js @@ -719,8 +719,8 @@ ${context.content.document.body.innerHTML}`, ); assert.equal(200, result.response.status); assertEquivalentNode( - new JSDOM('

Foo

Bar

Baz').window.document.body, new JSDOM(result.response.body).window.document.body, + new JSDOM('

Foo

Bar

Baz').window.document.body, ); }); }); diff --git a/test/testHTMLFromMarkdown.js b/test/testHTMLFromMarkdown.js index d1f839b00..529bfd4b6 100644 --- a/test/testHTMLFromMarkdown.js +++ b/test/testHTMLFromMarkdown.js @@ -143,7 +143,7 @@ describe('Testing Markdown conversion', () => { Hello World `, ` -

Hello

+

Hello

Hello World\n
`); await assertMd( @@ -166,10 +166,10 @@ describe('Testing Markdown conversion', () => { > > bar `, ` -

Foo

+

Foo

bar

-

Foo

+

Foo

bar

`); @@ -190,7 +190,7 @@ describe('Testing Markdown conversion', () => { Hello World [link](Foo +

Foo

Hello World [link](<foobar)

`); }); @@ -201,7 +201,7 @@ describe('Testing Markdown conversion', () => { Hello World [link](foo bar) `, ` -

Foo

+

Foo

Hello World [link](foo bar)

`); }); @@ -212,7 +212,7 @@ describe('Testing Markdown conversion', () => { Hello World [link](λ) `, ` -

Foo

+

Foo

Hello World link

`); }); @@ -225,7 +225,7 @@ describe('Testing Markdown conversion', () => { `, ` -

Foo

+

Foo

`); }); @@ -236,7 +236,7 @@ describe('Testing Markdown conversion', () => { Hello World [link](λ) `, ` -

Foo Bar

+

Foo Bar

Hello World link

`); }); @@ -296,8 +296,22 @@ describe('Testing Markdown conversion', () => { # location Foo `, ` -

location

+

location

Foo

`); }); + + it('Accept custom elements and attributes', async () => { + await assertMd(` + # Foo + Bar + Waldo + `, ` +

Foo

+

Bar

+

+ Waldo +

+ `); + }); });