Cadence Ember
327290e971
All checks were successful
continuous-integration/drone/push Build is passing
159 lines
4.4 KiB
JavaScript
159 lines
4.4 KiB
JavaScript
const {ejs, ElemJS} = require("../basic")
|
|
const {HighlightedCode} = require("./components")
|
|
const DOMPurify = require("dompurify")
|
|
const {resolveMxc} = require("../functions")
|
|
const {MatrixEvent} = require("./event")
|
|
|
|
const purifier = DOMPurify()
|
|
|
|
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 allowedElementAttributes = {
|
|
"FONT": ["data-mx-bg-color", "data-mx-color", "color"],
|
|
"SPAN": ["data-mx-bg-color", "data-mx-color"],
|
|
"A": ["name", "target", "href"],
|
|
"IMG": ["width", "height", "alt", "title", "src", "data-mx-emoticon"],
|
|
"OL": ["start"],
|
|
"CODE": ["class"],
|
|
}
|
|
|
|
const allowedAttributes = allowedElementAttributes[node.tagName] || []
|
|
hookevent.keepAttr = allowedAttributes.indexOf(hookevent.attrName) > -1
|
|
})
|
|
|
|
purifier.addHook("uponSanitizeElement", (node, hookevent, config) => {
|
|
// Remove bad classes from our code element
|
|
if (node.tagName === "CODE") {
|
|
node.classList.forEach(c => {
|
|
if (!c.startsWith("language-")) {
|
|
node.classList.remove(c)
|
|
}
|
|
})
|
|
}
|
|
if (node.tagName === "A") {
|
|
node.setAttribute("rel", "noopener") // prevent the opening page from accessing carbon
|
|
node.setAttribute("target", "_blank") // open in a new tab instead of replacing carbon
|
|
}
|
|
return node
|
|
})
|
|
|
|
function cleanHTML(html) {
|
|
const config = {
|
|
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",
|
|
// matrix tags
|
|
"mx-reply"
|
|
],
|
|
|
|
// In case we mess up in the uponSanitizeAttribute hook
|
|
ALLOWED_ATTR: [
|
|
"color", "name", "target", "href", "width", "height", "alt", "title",
|
|
"src", "start", "class", "noreferrer", "noopener",
|
|
// matrix attrs
|
|
"data-mx-emoticon", "data-mx-bg-color", "data-mx-color"
|
|
],
|
|
|
|
// Return a DOM fragment instead of a string, avoids potential future mutation XSS
|
|
// should also be faster than the browser parsing HTML twice
|
|
// https://research.securitum.com/mutation-xss-via-mathml-mutation-dompurify-2-0-17-bypass/
|
|
RETURN_DOM_FRAGMENT: true,
|
|
RETURN_DOM_IMPORT: true
|
|
}
|
|
return purifier.sanitize(html, config)
|
|
}
|
|
|
|
// Here we put all the processing of the messages that isn't as likely to potentially lead to security issues
|
|
function postProcessElements(element) {
|
|
element.querySelectorAll("pre").forEach(n => {
|
|
new HighlightedCode(n)
|
|
})
|
|
|
|
element.querySelectorAll("img").forEach(n => {
|
|
let src = n.getAttribute("src")
|
|
if (src) src = resolveMxc(src)
|
|
n.setAttribute("src", src)
|
|
})
|
|
|
|
element.querySelectorAll("font, span").forEach(n => {
|
|
const color = n.getAttribute("data-mx-color") || n.getAttribute("color")
|
|
const bgColor = n.getAttribute("data-mx-bg-color")
|
|
if (color) n.style.color = color
|
|
if (bgColor) n.style.backgroundColor = bgColor
|
|
})
|
|
}
|
|
|
|
|
|
class HTMLMessage extends MatrixEvent {
|
|
render() {
|
|
this.clearChildren()
|
|
|
|
let html = this.data.content.formatted_body
|
|
|
|
const fragment = cleanHTML(html)
|
|
postProcessElements(fragment)
|
|
|
|
this.child(ejs(fragment))
|
|
|
|
super.render()
|
|
}
|
|
|
|
static canRender(event) {
|
|
const content = event.content
|
|
return (
|
|
event.type === "m.room.message"
|
|
&& (content.msgtype === "m.text" || content.msgtype === "m.notice")
|
|
&& content.format === "org.matrix.custom.html"
|
|
&& content.formatted_body
|
|
)
|
|
}
|
|
|
|
canGroup() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
function autoLinkText(text) {
|
|
const fragment = ejs(new DocumentFragment())
|
|
let lastIndex = 0
|
|
text.replace(/https?:\/\/(?:[A-Za-z-]+\.)+[A-Za-z]{1,10}(?::[0-9]{1,6})?(?:\/[^ ]*)?/g, (url, index) => {
|
|
// add text before URL
|
|
fragment.addText(text.slice(lastIndex, index))
|
|
// add URL
|
|
fragment.child(
|
|
ejs("a")
|
|
.attribute("target", "_blank")
|
|
.attribute("noopener", "")
|
|
.attribute("href", url)
|
|
.addText(url)
|
|
)
|
|
// update state
|
|
lastIndex = index + url.length
|
|
})
|
|
// add final text
|
|
fragment.addText(text.slice(lastIndex))
|
|
return fragment
|
|
}
|
|
|
|
class TextMessage extends MatrixEvent {
|
|
render() {
|
|
this.clearChildren()
|
|
const fragment = autoLinkText(this.data.content.body)
|
|
this.child(fragment)
|
|
super.render()
|
|
}
|
|
|
|
static canRender(event) {
|
|
return event.type === "m.room.message"
|
|
}
|
|
|
|
canGroup() {
|
|
return true
|
|
}
|
|
}
|
|
|
|
module.exports = [HTMLMessage, TextMessage]
|