diff --git a/frontend/src/utils/htmlFromMarkdown.js b/frontend/src/utils/htmlFromMarkdown.js index b2a29bf65a..06e2464266 100644 --- a/frontend/src/utils/htmlFromMarkdown.js +++ b/frontend/src/utils/htmlFromMarkdown.js @@ -1,9 +1,59 @@ import { marked } from 'marked'; import DOMPurify from 'dompurify'; +const VIDEO_TAG_REGEXP = new RegExp(/^::youtube\[(.*)\]$/); + +const parseMarkdown = (markdownText) => { + marked.use({ + gfm: false, + extensions: [ + { + name: 'videoExt', + level: 'inline', + start: (src) => { + const m = src.match(/^::youtube/); + + if (m) { + return m.index; + } + }, + tokenizer: function(src) { + const match = VIDEO_TAG_REGEXP.exec(src); + + if (match) { + return { + type: 'videoExt', + raw: match[0], + text: match[1].trim(), + tokens: [] + }; + } + }, + renderer: function(token) { + const videoId = token.text; + + return ` + + `; + } + } + ] + }); + return marked.parse(markdownText); +} + /* per https://stackoverflow.com/a/34688574/272018 */ export const htmlFromMarkdown = (markdownText) => { DOMPurify.addHook('afterSanitizeAttributes', function (node) { + // set all elements owning target to target=_blank if ('target' in node) { node.setAttribute('target', '_blank'); @@ -16,7 +66,23 @@ export const htmlFromMarkdown = (markdownText) => { node.setAttribute('xlink:show', 'new'); } }); - return { __html: DOMPurify.sanitize(marked.parse(markdownText)) }; + + DOMPurify.addHook("uponSanitizeElement", (node, data) => { + if (data.tagName === "iframe") { + const src = node.getAttribute("src") || ""; + // allow only youtube urls to be embedded in iframes + if (!src.startsWith("https://www.youtube.com/embed/")) { + return node.parentNode?.removeChild(node); + } + } + }); + + const config = { + ADD_TAGS: ['iframe'], + ADD_ATTR: ["allow", "allowfullscreen", "frameborder"] + }; + + return { __html: DOMPurify.sanitize(parseMarkdown(markdownText), config) }; }; export const formatUserNamesToLink = (text) => { diff --git a/frontend/src/utils/tests/htmlFromMarkdown.test.js b/frontend/src/utils/tests/htmlFromMarkdown.test.js index e265e2912e..bad55434f6 100644 --- a/frontend/src/utils/tests/htmlFromMarkdown.test.js +++ b/frontend/src/utils/tests/htmlFromMarkdown.test.js @@ -9,6 +9,29 @@ test('htmlFromMarkdown returns correct content', () => { ); }); +test('htmlFromMarkdown with youtube tag', () => { + const html = htmlFromMarkdown('::youtube[UzT0i5XhsOQ]').__html; + expect(html).toContain(' { + const html = htmlFromMarkdown('').__html; + expect(html).not.toContain(' { expect( formatUserNamesToLink(