Compare commits
	
		
			4 commits
		
	
	
		
			098ea88f5d
			...
			e08b895694
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e08b895694 | |||
| d983385e16 | |||
| f46f9abe6e | |||
| 1a8427925c | 
					 5 changed files with 78 additions and 50 deletions
				
			
		| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
const {Event} = require("./event")
 | 
					const {MatrixEvent} = require("./event")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EncryptedMessage extends Event {
 | 
					class EncryptedMessage extends MatrixEvent {
 | 
				
			||||||
	render() {
 | 
						render() {
 | 
				
			||||||
		super.render()
 | 
							super.render()
 | 
				
			||||||
		return this.text("Carbon cannot render encrypted messages yet")
 | 
							return this.text("Carbon cannot render encrypted messages yet")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
const {ElemJS, ejs} = require("../basic")
 | 
					const {ElemJS, ejs} = require("../basic")
 | 
				
			||||||
const {dateFormatter} = require("../dateFormatter")
 | 
					const {dateFormatter} = require("../dateFormatter")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Event extends ElemJS {
 | 
					class MatrixEvent extends ElemJS {
 | 
				
			||||||
	constructor(data) {
 | 
						constructor(data) {
 | 
				
			||||||
		super("div")
 | 
							super("div")
 | 
				
			||||||
		this.class("c-message")
 | 
							this.class("c-message")
 | 
				
			||||||
| 
						 | 
					@ -50,10 +50,17 @@ class Event extends ElemJS {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function simpleEvent(filter, render) {
 | 
				
			||||||
 | 
						return class extends MatrixEvent {
 | 
				
			||||||
 | 
							render() {
 | 
				
			||||||
 | 
								super.render()
 | 
				
			||||||
 | 
								return this.text(render(this.data))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							static canRender(event) {
 | 
				
			||||||
function renderEvent(event) {
 | 
								return filter(event)
 | 
				
			||||||
	return new events.find(e => e.canRender(event))(event)
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {renderEvent, Event}
 | 
					module.exports = {MatrixEvent, simpleEvent}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,13 @@
 | 
				
			||||||
const {Event} = require("./event")
 | 
					const {simpleEvent} = require("./event")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createMembershipEvent(membership, text) {
 | 
					const UnknownMembership = simpleEvent((e) => e.type == "m.room.member", (e) => "unknown membership event")
 | 
				
			||||||
	return class extends Event {
 | 
					 | 
				
			||||||
		render() {
 | 
					 | 
				
			||||||
			super.render()
 | 
					 | 
				
			||||||
			return this.text(text(this.data))
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		static canRender(event) {
 | 
					function createMembershipEvent(membership, message) {
 | 
				
			||||||
			return event.type == "m.room.member" && event.content.membership == membership
 | 
						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 InvitedEvent = createMembershipEvent("invite", (e) => `invited ${e.content.displayname} the room`)
 | 
				
			||||||
const LeaveEvent = createMembershipEvent("leave", () => "left the room")
 | 
					const LeaveEvent = createMembershipEvent("leave", () => "left the room")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = [JoinedEvent, InvitedEvent, LeaveEvent]
 | 
					module.exports = [JoinedEvent, InvitedEvent, LeaveEvent, UnknownMembership]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,33 +1,73 @@
 | 
				
			||||||
const {ejs} = require("../basic")
 | 
					const {ejs} = require("../basic")
 | 
				
			||||||
const DOMPurify = require("dompurify")
 | 
					const DOMPurify = require("dompurify")
 | 
				
			||||||
const {resolveMxc} = require("../functions")
 | 
					const {resolveMxc} = require("../functions")
 | 
				
			||||||
const {Event} = require("./event")
 | 
					const {MatrixEvent} = require("./event")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const purifier = DOMPurify()
 | 
					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) => {
 | 
					purifier.addHook("afterSanitizeAttributes", (node, hookevent, config) => {
 | 
				
			||||||
	if (node.tagName == "img") {
 | 
						if (node.tagName == "IMG") {
 | 
				
			||||||
		let src = node.getAttribute("src")
 | 
							let src = node.getAttribute("src")
 | 
				
			||||||
		if (src) src = resolveMxc(src)
 | 
							if (src) src = resolveMxc(src)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		node.setAttribute("src", src)
 | 
							node.setAttribute("src", src)
 | 
				
			||||||
 | 
						} else if (node.tagName == "A") {
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (node.tagName = "a") {
 | 
					 | 
				
			||||||
		node.setAttribute("rel", "noopener")
 | 
							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
 | 
						return node
 | 
				
			||||||
 | 
					 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function sanitize(html) {
 | 
					 | 
				
			||||||
	return purifier.sanitize(html, DOMPURIFY_CONFIG)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
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 {
 | 
					function sanitize(html) {
 | 
				
			||||||
 | 
						return purifier.sanitize(html)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class HTMLMessage extends MatrixEvent {
 | 
				
			||||||
	render() {
 | 
						render() {
 | 
				
			||||||
		super.render()
 | 
							super.render()
 | 
				
			||||||
		let html = this.data.content.formatted_body
 | 
							let html = this.data.content.formatted_body
 | 
				
			||||||
| 
						 | 
					@ -49,7 +89,7 @@ class HTMLMessage extends Event {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TextMessage extends Event {
 | 
					class TextMessage extends MatrixEvent {
 | 
				
			||||||
	render() {
 | 
						render() {
 | 
				
			||||||
		super.render()
 | 
							super.render()
 | 
				
			||||||
		return this.text(this.data.content.body)
 | 
							return this.text(this.data.content.body)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,15 +1,4 @@
 | 
				
			||||||
const {Event} = require("./event")
 | 
					const {simpleEvent} = require("./event")
 | 
				
			||||||
 | 
					const UnknownEvent = simpleEvent(() => true, () => "Cannot render event")
 | 
				
			||||||
class UnknownEvent extends Event {
 | 
					console.log(UnknownEvent)
 | 
				
			||||||
	render() {
 | 
					 | 
				
			||||||
		super.render()
 | 
					 | 
				
			||||||
		this.text("Cannot render event")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	static canRender(_event) {
 | 
					 | 
				
			||||||
		return true
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = [UnknownEvent]
 | 
					module.exports = [UnknownEvent]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue