From 1a8427925c30c62ac29fc867ce4fa64dc3f6f258 Mon Sep 17 00:00:00 2001 From: Bad Date: Mon, 26 Oct 2020 22:55:27 +0100 Subject: [PATCH 1/4] Add unknown memberships --- src/js/events/membership.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/js/events/membership.js b/src/js/events/membership.js index 97daea3..9b554cf 100644 --- a/src/js/events/membership.js +++ b/src/js/events/membership.js @@ -1,10 +1,10 @@ const {Event} = require("./event") -function createMembershipEvent(membership, text) { +function createMembershipEvent(membership, message) { return class extends Event { render() { super.render() - return this.text(text(this.data)) + return this.text(message(this.data)) } static canRender(event) { @@ -13,9 +13,20 @@ function createMembershipEvent(membership, text) { } } +class UnknownMembership extends Event { + render() { + super.render() + return this.text("Unsupported membership event") + } + + static canRender(event) { + return event.type == "m.room.member" + } + +} const JoinedEvent = createMembershipEvent("join", () => "joined the room") const InvitedEvent = createMembershipEvent("invite", (e) => `invited ${e.content.displayname} the room`) const LeaveEvent = createMembershipEvent("leave", () => "left the room") -module.exports = [JoinedEvent, InvitedEvent, LeaveEvent] +module.exports = [JoinedEvent, InvitedEvent, LeaveEvent, UnknownMembership] From f46f9abe6efa2e93e38ee9a92ad5399b02cddbd4 Mon Sep 17 00:00:00 2001 From: Bad Date: Mon, 26 Oct 2020 22:55:54 +0100 Subject: [PATCH 2/4] Improve rich text rendering to more closely match the recommendations from the spec --- src/js/events/message.js | 60 +++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 10 deletions(-) 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() { From d983385e166857edcb4ee329c4b8970f8a41b0c6 Mon Sep 17 00:00:00 2001 From: Bad Date: Mon, 26 Oct 2020 22:58:38 +0100 Subject: [PATCH 3/4] Fix compiler warnings --- src/js/events/encrypted.js | 4 ++-- src/js/events/event.js | 8 ++------ src/js/events/membership.js | 6 +++--- src/js/events/message.js | 6 +++--- src/js/events/unknown.js | 4 ++-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/js/events/encrypted.js b/src/js/events/encrypted.js index 32dbe19..6d9d6d0 100644 --- a/src/js/events/encrypted.js +++ b/src/js/events/encrypted.js @@ -1,6 +1,6 @@ -const {Event} = require("./event") +const {MatrixEvent} = require("./event") -class EncryptedMessage extends Event { +class EncryptedMessage extends MatrixEvent { render() { super.render() return this.text("Carbon cannot render encrypted messages yet") diff --git a/src/js/events/event.js b/src/js/events/event.js index ddb412d..06d8a50 100644 --- a/src/js/events/event.js +++ b/src/js/events/event.js @@ -1,7 +1,7 @@ const {ElemJS, ejs} = require("../basic") const {dateFormatter} = require("../dateFormatter") -class Event extends ElemJS { +class MatrixEvent extends ElemJS { constructor(data) { super("div") this.class("c-message") @@ -52,8 +52,4 @@ class Event extends ElemJS { -function renderEvent(event) { - return new events.find(e => e.canRender(event))(event) -} - -module.exports = {renderEvent, Event} +module.exports = {MatrixEvent} diff --git a/src/js/events/membership.js b/src/js/events/membership.js index 9b554cf..285fc9e 100644 --- a/src/js/events/membership.js +++ b/src/js/events/membership.js @@ -1,7 +1,7 @@ -const {Event} = require("./event") +const {MatrixEvent} = require("./event") function createMembershipEvent(membership, message) { - return class extends Event { + return class extends MatrixEvent { render() { super.render() return this.text(message(this.data)) @@ -13,7 +13,7 @@ function createMembershipEvent(membership, message) { } } -class UnknownMembership extends Event { +class UnknownMembership extends MatrixEvent { render() { super.render() return this.text("Unsupported membership event") diff --git a/src/js/events/message.js b/src/js/events/message.js index 549b2a0..c50ec57 100644 --- a/src/js/events/message.js +++ b/src/js/events/message.js @@ -1,7 +1,7 @@ const {ejs} = require("../basic") const DOMPurify = require("dompurify") const {resolveMxc} = require("../functions") -const {Event} = require("./event") +const {MatrixEvent} = require("./event") const purifier = DOMPurify() @@ -67,7 +67,7 @@ function sanitize(html) { return purifier.sanitize(html) } -class HTMLMessage extends Event { +class HTMLMessage extends MatrixEvent { render() { super.render() let html = this.data.content.formatted_body @@ -89,7 +89,7 @@ class HTMLMessage extends Event { } -class TextMessage extends Event { +class TextMessage extends MatrixEvent { render() { super.render() return this.text(this.data.content.body) diff --git a/src/js/events/unknown.js b/src/js/events/unknown.js index d644de7..6fb1a02 100644 --- a/src/js/events/unknown.js +++ b/src/js/events/unknown.js @@ -1,6 +1,6 @@ -const {Event} = require("./event") +const {MatrixEvent} = require("./event") -class UnknownEvent extends Event { +class UnknownEvent extends MatrixEvent { render() { super.render() this.text("Cannot render event") From e08b8956943e08f96b1f40a0b663814b895bfdfe Mon Sep 17 00:00:00 2001 From: Bad Date: Mon, 26 Oct 2020 23:16:47 +0100 Subject: [PATCH 4/4] Create a simple event shorthand --- src/js/events/event.js | 13 ++++++++++++- src/js/events/membership.js | 29 +++++------------------------ src/js/events/unknown.js | 17 +++-------------- 3 files changed, 20 insertions(+), 39 deletions(-) diff --git a/src/js/events/event.js b/src/js/events/event.js index 06d8a50..4ec6c54 100644 --- a/src/js/events/event.js +++ b/src/js/events/event.js @@ -50,6 +50,17 @@ class MatrixEvent extends ElemJS { } } +function simpleEvent(filter, render) { + return class extends MatrixEvent { + render() { + super.render() + return this.text(render(this.data)) + } + static canRender(event) { + return filter(event) + } + } +} -module.exports = {MatrixEvent} +module.exports = {MatrixEvent, simpleEvent} diff --git a/src/js/events/membership.js b/src/js/events/membership.js index 285fc9e..a81a3ca 100644 --- a/src/js/events/membership.js +++ b/src/js/events/membership.js @@ -1,31 +1,12 @@ -const {MatrixEvent} = require("./event") +const {simpleEvent} = require("./event") + +const UnknownMembership = simpleEvent((e) => e.type == "m.room.member", (e) => "unknown membership event") function createMembershipEvent(membership, message) { - return class extends MatrixEvent { - render() { - super.render() - return this.text(message(this.data)) - } - - static canRender(event) { - return event.type == "m.room.member" && event.content.membership == membership - } - - } -} -class UnknownMembership extends MatrixEvent { - render() { - super.render() - return this.text("Unsupported membership event") - } - - static canRender(event) { - return event.type == "m.room.member" - } - + return simpleEvent((e) => e.type == "m.room.member" && e.content.membership === membership, message) } -const JoinedEvent = createMembershipEvent("join", () => "joined the room") +const JoinedEvent = createMembershipEvent("join", (e) => {console.log(e); return "joined the room"}) const InvitedEvent = createMembershipEvent("invite", (e) => `invited ${e.content.displayname} the room`) const LeaveEvent = createMembershipEvent("leave", () => "left the room") diff --git a/src/js/events/unknown.js b/src/js/events/unknown.js index 6fb1a02..bbc40f8 100644 --- a/src/js/events/unknown.js +++ b/src/js/events/unknown.js @@ -1,15 +1,4 @@ -const {MatrixEvent} = require("./event") - -class UnknownEvent extends MatrixEvent { - render() { - super.render() - this.text("Cannot render event") - } - - static canRender(_event) { - return true - } - -} - +const {simpleEvent} = require("./event") +const UnknownEvent = simpleEvent(() => true, () => "Cannot render event") +console.log(UnknownEvent) module.exports = [UnknownEvent]