diff --git a/CHANGELOG.md b/CHANGELOG.md index 40091280..443890c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Fixed - Refactor auto scaling component ([#276](https://github.com/marp-team/marp-core/pull/276)) +- Preserve HTML comments within html block ([#282](https://github.com/marp-team/marp-core/pull/282)) ### Changed diff --git a/src/html/html.ts b/src/html/html.ts index 22420417..b61ae472 100644 --- a/src/html/html.ts +++ b/src/html/html.ts @@ -22,6 +22,26 @@ export function markdown(md): void { (original: (...args: any[]) => string) => (...args) => { const ret = original(...args) + + // Pick comments + const splitted: string[] = [] + let pos = 0 + + while (pos < ret.length) { + const startIdx = ret.indexOf('', startIdx + 4) : -1 + + if (endIdx === -1) { + splitted.push(ret.slice(pos)) + break + } + + endIdx += 3 + splitted.push(ret.slice(pos, startIdx), ret.slice(startIdx, endIdx)) + pos = endIdx + } + + // Apply filter to each contents by XSS const allowList = {} const html: MarpOptions['html'] = md.options.html @@ -58,8 +78,17 @@ export function markdown(md): void { }, }) - const sanitized = filter.process(ret) - return md.options.xhtmlOut ? xhtmlOutFilter.process(sanitized) : sanitized + return splitted + .map((part, idx) => { + if (idx % 2 === 1) return part + + const sanitized = filter.process(part) + + return md.options.xhtmlOut + ? xhtmlOutFilter.process(sanitized) + : sanitized + }) + .join('') } md.renderer.rules.html_inline = sanitizedRenderer(html_inline) diff --git a/test/marp.ts b/test/marp.ts index 77e8d8ef..006a1553 100644 --- a/test/marp.ts +++ b/test/marp.ts @@ -215,6 +215,56 @@ describe('Marp', () => { expect($('header > strong')).toHaveLength(1) expect($('footer > em')).toHaveLength(1) }) + + it('keeps raw HTML comments within valid HTML block', () => { + const { html: $script, comments: comments$script } = marp().render( + "" + ) + expect($script).toContain("const script = 'test'") + expect(comments$script[0]).toHaveLength(0) + + // Complex comment + const complexComment = ` + +`.trim() + const { html: $complex } = marp().render( + `` + ) + expect($complex).toContain(complexComment) + + // NOTE: Marpit framework will collect the comment block if the whole of HTML block was comment + const { html: $comment, comments: comments$comment } = marp().render( + "" + ) + expect($comment).not.toContain("const script = 'test'") + expect(comments$comment[0]).toHaveLength(1) + }) + + it('sanitizes CDATA section', () => { + // HTML Living Standard denys using CDATA in HTML context so must be sanitized + const cdata = ` +XSS
+ +]]> +`.trim() + const { html } = marp().render(cdata) + expect(html).not.toContain(cdata) + }) }) describe('with true', () => {