diff --git a/src/js/events/message.js b/src/js/events/message.js index 0218a60..549b2a0 100644 --- a/src/js/events/message.js +++ b/src/js/events/message.js @@ -4,28 +4,68 @@ const {resolveMxc} = require("../functions") const {Event} = require("./event") const purifier = DOMPurify() + +purifier.setConfig({ + ALLOWED_URI_REGEXP: /^mxc:\/\/[a-zA-Z0-9\.]+\/[a-zA-Z0-9]+$/, // As per the spec we only allow mxc uris + ALLOWED_TAGS: ['font', 'del', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'caption', 'pre', 'span', 'img'], + + // In case we mess up none of those attributes should read to XSS + ALLOWED_ATTR: ["data-mx-bg-color", "data-mx-color", "color", + "name", "target", "href", + "width", "height", "alt", "title", "src", "data-mx-emoticon", + "start", "class"], + //Custom config option that allows the array of attributes for a given tag + ALLOWED_ATTR_CUSTOM: { + "FONT": ["data-mx-bg-color", "data-mx-color", "color"], + "SPAN": ["data-mx-bg-color", "data-mx-color", "color"], + "A": ["name", "target", "href"], + "IMG": ["width", "height", "alt", "title", "src", "data-mx-emoticon"], + "OL": ["start"], + "CODE": ["class"], + } +}) + +//Handle our custom tag +purifier.addHook("uponSanitizeAttribute", (node, hookevent, config) => { + //If purifier already rejected an attribute there is no point in checking it + if (hookevent.keepAttr === false) return; + + const allowed_attributes = config.ALLOWED_ATTR_CUSTOM[node.tagName] || [] + hookevent.keepAttr = allowed_attributes.indexOf(hookevent.attrName) > -1; +}) + +//Remove bad classes from our code element +purifier.addHook("uponSanitizeElement", (node, hookevent, config) => { + if (node.tagName != "CODE") return + node.classList.forEach(c => { + if (!c.startsWith("language-")) { + node.classList.remove(c) + } + }) + return node +}) + purifier.addHook("afterSanitizeAttributes", (node, hookevent, config) => { - if (node.tagName == "img") { + if (node.tagName == "IMG") { let src = node.getAttribute("src") if (src) src = resolveMxc(src) node.setAttribute("src", src) - - } - if (node.tagName = "a") { + } else if (node.tagName == "A") { node.setAttribute("rel", "noopener") + } else if (node.tagName == "FONT" || node.tagName == "SPAN") { + const color = node.getAttribute("data-mx-color") + const bgColor = node.getAttribute("data-mx-bg-color") + if (color) node.style.color = color; + if (bgColor) node.style.backgroundColor = bgColor; } return node - }) + function sanitize(html) { - return purifier.sanitize(html, DOMPURIFY_CONFIG) + return purifier.sanitize(html) } -const DOMPURIFY_CONFIG = { - ALLOWED_URI_REGEXP: /^mxc:\/\/[a-zA-Z0-9\.]+\/[a-zA-Z0-9]+$/, // As per the spec we only allow mxc uris - ALLOWED_TAGS: ['font', 'del', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'caption', 'pre', 'span', 'img'], -}; class HTMLMessage extends Event { render() {