From c0a8deb3470213373a34d1f5ef5283f24ebd690f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 20 Oct 2024 12:34:12 +0300 Subject: [PATCH] web/html: remove old client-side html sanitizer --- web/package-lock.json | 58 +++--------- web/package.json | 2 - web/src/util/html.ts | 210 ------------------------------------------ 3 files changed, 12 insertions(+), 258 deletions(-) delete mode 100644 web/src/util/html.ts diff --git a/web/package-lock.json b/web/package-lock.json index dc2632e..28a347e 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -11,11 +11,9 @@ "dependencies": { "@types/react": "npm:types-react@rc", "@types/react-dom": "npm:types-react-dom@rc", - "linkify-react": "^4.1.3", "react": "^19.0.0-rc-0751fac7-20241002", "react-dom": "^19.0.0-rc-0751fac7-20241002", "react-spinners": "^0.14.1", - "sanitize-html": "^2.13.1", "unhomoglyph": "^1.0.6" }, "devDependencies": { @@ -2418,14 +2416,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2476,6 +2466,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -2489,6 +2480,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, "funding": [ { "type": "github", @@ -2500,6 +2492,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -2514,6 +2507,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -2543,6 +2537,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "engines": { "node": ">=0.12" }, @@ -2743,6 +2738,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -3384,6 +3380,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -3619,14 +3616,6 @@ "node": ">=8" } }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -3821,15 +3810,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "node_modules/linkify-react": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/linkify-react/-/linkify-react-4.1.3.tgz", - "integrity": "sha512-rhI3zM/fxn5BfRPHfi4r9N7zgac4vOIxub1wHIWXLA5ENTMs+BGaIaFO1D1PhmxgwhIKmJz3H7uCP0Dg5JwSlA==", - "peerDependencies": { - "linkifyjs": "^4.0.0", - "react": ">= 15.0.0" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3922,6 +3902,7 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, "funding": [ { "type": "github", @@ -4122,11 +4103,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4163,7 +4139,8 @@ "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -4190,6 +4167,7 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4426,19 +4404,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sanitize-html": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.1.tgz", - "integrity": "sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==", - "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, "node_modules/scheduler": { "version": "0.25.0-rc-0751fac7-20241002", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-0751fac7-20241002.tgz", @@ -4541,6 +4506,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "engines": { "node": ">=0.10.0" } diff --git a/web/package.json b/web/package.json index 62c81fd..d4af94d 100644 --- a/web/package.json +++ b/web/package.json @@ -13,11 +13,9 @@ "dependencies": { "@types/react": "npm:types-react@rc", "@types/react-dom": "npm:types-react-dom@rc", - "linkify-react": "^4.1.3", "react": "^19.0.0-rc-0751fac7-20241002", "react-dom": "^19.0.0-rc-0751fac7-20241002", "react-spinners": "^0.14.1", - "sanitize-html": "^2.13.1", "unhomoglyph": "^1.0.6" }, "devDependencies": { diff --git a/web/src/util/html.ts b/web/src/util/html.ts deleted file mode 100644 index 7f24b4e..0000000 --- a/web/src/util/html.ts +++ /dev/null @@ -1,210 +0,0 @@ -// gomuks - A Matrix client written in Go. -// Copyright (C) 2024 Tulir Asokan -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -// From matrix-react-sdk, Copyright 2024 The Matrix.org Foundation C.I.C. -// Originally licensed under the Apache License, Version 2.0 -// https://github.com/matrix-org/matrix-react-sdk/blob/develop/src/Linkify.tsx#L245 -import sanitizeHtml from "sanitize-html" -import { getMediaURL } from "../api/media.ts" -import { calculateMediaSize } from "./mediasize.ts" - -const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/ - -export const PERMITTED_URL_SCHEMES = [ - "bitcoin", - "ftp", - "geo", - "http", - "https", - "im", - "irc", - "ircs", - "magnet", - "mailto", - "matrix", - "mms", - "news", - "nntp", - "openpgp4fpr", - "sip", - "sftp", - "sms", - "smsto", - "ssh", - "tel", - "urn", - "webcal", - "wtai", - "xmpp", -] - -export const transformTags: NonNullable = { - "a": function(tagName: string, attribs: sanitizeHtml.Attributes) { - if (attribs.href) { - attribs.target = "_blank" - } else { - // Delete the href attrib if it is falsy - delete attribs.href - } - - attribs.rel = "noreferrer noopener" // https://mathiasbynens.github.io/rel-noopener/ - return { tagName, attribs } - }, - "img": function(tagName: string, attribs: sanitizeHtml.Attributes) { - const src = attribs.src - if (!src.startsWith("mxc://")) { - return { - tagName, - attribs: {}, - } - } - - const requestedWidth = Number(attribs.width) - const requestedHeight = Number(attribs.height) - if (requestedHeight && requestedHeight <= 48) { - attribs.style = `height: ${requestedHeight}px; width: auto; max-width: ${2 * requestedHeight}px;` - } - const style = calculateMediaSize(requestedWidth, requestedHeight) - if (style.media.aspectRatio) { - attribs.style = `width: ${style.container.width}; height: ${style.container.height};` - } else { - attribs.style = `height: 24px; width: auto; max-width: 48px;` - } - - attribs.src = getMediaURL(src)! - attribs.loading = "lazy" - return { tagName, attribs } - }, - "code": function(tagName: string, attribs: sanitizeHtml.Attributes) { - if (typeof attribs.class !== "undefined") { - // Filter out all classes other than ones starting with language- for syntax highlighting. - const classes = attribs.class.split(/\s/).filter(function(cl) { - return cl.startsWith("language-") && !cl.startsWith("language-_") - }) - attribs.class = classes.join(" ") - } - return { tagName, attribs } - }, - "*": function(tagName: string, attribs: sanitizeHtml.Attributes) { - // Delete any style previously assigned, style is an allowedTag for font, span & img, - // because attributes are stripped after transforming. - // For img this is trusted as it is generated wholly within the img transformation method. - if (tagName !== "img") { - delete attribs.style - } - - if (tagName === "span") { - attribs.title = attribs["data-mx-spoiler"] - } - - // Sanitise and transform data-mx-color and data-mx-bg-color to their CSS - // equivalents - const customCSSMapper: Record = { - "data-mx-color": "color", - "data-mx-bg-color": "background-color", - // $customAttributeKey: $cssAttributeKey - } - - let style = "" - for (const [customAttributeKey, cssAttributeKey] of Object.entries(customCSSMapper)) { - const customAttributeValue = attribs[customAttributeKey] - if ( - customAttributeValue && - typeof customAttributeValue === "string" && - COLOR_REGEX.test(customAttributeValue) - ) { - style += cssAttributeKey + ":" + customAttributeValue + ";" - delete attribs[customAttributeKey] - } - } - - if (style) { - attribs.style = style + (attribs.style || "") - } - - return { tagName, attribs } - }, -} - -export const sanitizeHtmlParams: sanitizeHtml.IOptions = { - allowedTags: [ - // These tags are suggested by the spec https://spec.matrix.org/v1.12/client-server-api/#mroommessage-msgtypes - "font", - "del", - "s", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "blockquote", - "p", - "a", - "ul", - "ol", - "sup", - "sub", - "nl", - "li", - "b", - "i", - "u", - "strong", - "em", - "strike", - "code", - "hr", - "br", - "div", - "table", - "thead", - "caption", - "tbody", - "tr", - "th", - "td", - "pre", - "span", - "img", - "details", - "summary", - ], - allowedAttributes: { - // attribute sanitization happens after transformations, so we have to accept `style` for font, span & img - // but strip during the transformation. - // custom ones first: - font: ["color", "data-mx-bg-color", "data-mx-color", "style"], // custom to matrix - span: [ - "data-mx-maths", "data-mx-bg-color", "data-mx-color", "data-mx-spoiler", "style", "title", - ], // custom to matrix - div: ["data-mx-maths"], - // eslint-disable-next-line id-length - a: ["href", "name", "target", "rel"], // remote target: custom to matrix - // img tags also accept width/height, we just map those to max-width & max-height during transformation - img: ["src", "alt", "title", "style", "loading", "data-mx-emoticon"], - ol: ["start"], - code: ["class"], // We don't actually allow all classes, we filter them in transformTags - }, - // Lots of these won't come up by default because we don't allow them - selfClosing: ["img", "br", "hr", "area", "base", "basefont", "input", "link", "meta"], - // URL schemes we permit - allowedSchemes: PERMITTED_URL_SCHEMES, - allowProtocolRelative: false, - transformTags, - // 50 levels deep "should be enough for anyone" - nestingLimit: 50, -}