Merge branch 'dompurify' into 'main'
Add DOMPurify See merge request soapbox-pub/soapbox!2930
This commit is contained in:
commit
6210ad3c25
7 changed files with 80 additions and 9 deletions
|
@ -124,6 +124,7 @@
|
||||||
"intersection-observer": "^0.12.2",
|
"intersection-observer": "^0.12.2",
|
||||||
"intl-messageformat": "10.5.8",
|
"intl-messageformat": "10.5.8",
|
||||||
"intl-pluralrules": "^2.0.0",
|
"intl-pluralrules": "^2.0.0",
|
||||||
|
"isomorphic-dompurify": "^2.3.0",
|
||||||
"leaflet": "^1.8.0",
|
"leaflet": "^1.8.0",
|
||||||
"lexical": "^0.12.4",
|
"lexical": "^0.12.4",
|
||||||
"line-awesome": "^1.3.0",
|
"line-awesome": "^1.3.0",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Markup from 'soapbox/components/markup';
|
import Markup from 'soapbox/components/markup';
|
||||||
|
@ -9,7 +10,7 @@ import { LogoText } from './logo-text';
|
||||||
|
|
||||||
const SiteBanner: React.FC = () => {
|
const SiteBanner: React.FC = () => {
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const description = instance.description;
|
const description = DOMPurify.sanitize(instance.description);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack space={3}>
|
<Stack space={3}>
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
Record as ImmutableRecord,
|
Record as ImmutableRecord,
|
||||||
fromJS,
|
fromJS,
|
||||||
} from 'immutable';
|
} from 'immutable';
|
||||||
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
|
|
||||||
import emojify from 'soapbox/features/emoji';
|
import emojify from 'soapbox/features/emoji';
|
||||||
import { normalizeAttachment } from 'soapbox/normalizers/attachment';
|
import { normalizeAttachment } from 'soapbox/normalizers/attachment';
|
||||||
|
@ -60,8 +61,8 @@ const normalizeStatusPoll = (statusEdit: ImmutableMap<string, any>) => {
|
||||||
|
|
||||||
const normalizeContent = (statusEdit: ImmutableMap<string, any>) => {
|
const normalizeContent = (statusEdit: ImmutableMap<string, any>) => {
|
||||||
const emojiMap = makeEmojiMap(statusEdit.get('emojis'));
|
const emojiMap = makeEmojiMap(statusEdit.get('emojis'));
|
||||||
const contentHtml = stripCompatibilityFeatures(emojify(statusEdit.get('content'), emojiMap));
|
const contentHtml = DOMPurify.sanitize(stripCompatibilityFeatures(emojify(statusEdit.get('content'), emojiMap)), { ADD_ATTR: ['target'] });
|
||||||
const spoilerHtml = emojify(escapeTextContentForBrowser(statusEdit.get('spoiler_text')), emojiMap);
|
const spoilerHtml = DOMPurify.sanitize(emojify(escapeTextContentForBrowser(statusEdit.get('spoiler_text')), emojiMap), { ADD_ATTR: ['target'] });
|
||||||
|
|
||||||
return statusEdit
|
return statusEdit
|
||||||
.set('contentHtml', contentHtml)
|
.set('contentHtml', contentHtml)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
|
|
||||||
import emojify from 'soapbox/features/emoji';
|
import emojify from 'soapbox/features/emoji';
|
||||||
import { normalizeStatus } from 'soapbox/normalizers';
|
import { normalizeStatus } from 'soapbox/normalizers';
|
||||||
|
@ -119,8 +120,8 @@ export const calculateStatus = (
|
||||||
|
|
||||||
return status.merge({
|
return status.merge({
|
||||||
search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent || '',
|
search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent || '',
|
||||||
contentHtml: stripCompatibilityFeatures(emojify(status.content, emojiMap)),
|
contentHtml: DOMPurify.sanitize(stripCompatibilityFeatures(emojify(status.content, emojiMap)), { USE_PROFILES: { html: true } }),
|
||||||
spoilerHtml: emojify(escapeTextContentForBrowser(spoilerText), emojiMap),
|
spoilerHtml: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(spoilerText), emojiMap), { USE_PROFILES: { html: true } }),
|
||||||
hidden: expandSpoilers ? false : spoilerText.length > 0 || status.sensitive,
|
hidden: expandSpoilers ? false : spoilerText.length > 0 || status.sensitive,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
|
|
||||||
import emojify from 'soapbox/features/emoji';
|
import emojify from 'soapbox/features/emoji';
|
||||||
|
@ -115,7 +116,7 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
|
||||||
|
|
||||||
const newFields = fields.map((field) => ({
|
const newFields = fields.map((field) => ({
|
||||||
...field,
|
...field,
|
||||||
name_emojified: emojify(escapeTextContentForBrowser(field.name), customEmojiMap),
|
name_emojified: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(field.name), customEmojiMap), { USE_PROFILES: { html: true } }),
|
||||||
value_emojified: emojify(field.value, customEmojiMap),
|
value_emojified: emojify(field.value, customEmojiMap),
|
||||||
value_plain: unescapeHTML(field.value),
|
value_plain: unescapeHTML(field.value),
|
||||||
}));
|
}));
|
||||||
|
@ -133,7 +134,7 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
|
||||||
avatar_static: account.avatar_static || account.avatar,
|
avatar_static: account.avatar_static || account.avatar,
|
||||||
discoverable: account.discoverable || account.source?.pleroma?.discoverable || false,
|
discoverable: account.discoverable || account.source?.pleroma?.discoverable || false,
|
||||||
display_name: displayName,
|
display_name: displayName,
|
||||||
display_name_html: emojify(escapeTextContentForBrowser(displayName), customEmojiMap),
|
display_name_html: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(displayName), customEmojiMap), { USE_PROFILES: { html: true } }),
|
||||||
domain,
|
domain,
|
||||||
fields: newFields,
|
fields: newFields,
|
||||||
fqn: account.fqn || (account.acct.includes('@') ? account.acct : `${account.acct}@${domain}`),
|
fqn: account.fqn || (account.acct.includes('@') ? account.acct : `${account.acct}@${domain}`),
|
||||||
|
@ -141,7 +142,7 @@ const transformAccount = <T extends TransformableAccount>({ pleroma, other_setti
|
||||||
moderator: pleroma?.is_moderator || false,
|
moderator: pleroma?.is_moderator || false,
|
||||||
local: pleroma?.is_local !== undefined ? pleroma.is_local : account.acct.split('@')[1] === undefined,
|
local: pleroma?.is_local !== undefined ? pleroma.is_local : account.acct.split('@')[1] === undefined,
|
||||||
location: account.location || pleroma?.location || other_settings?.location || '',
|
location: account.location || pleroma?.location || other_settings?.location || '',
|
||||||
note_emojified: emojify(account.note, customEmojiMap),
|
note_emojified: DOMPurify.sanitize(emojify(account.note, customEmojiMap), { USE_PROFILES: { html: true } }),
|
||||||
pleroma: (() => {
|
pleroma: (() => {
|
||||||
if (!pleroma) return undefined;
|
if (!pleroma) return undefined;
|
||||||
const { relationship, ...rest } = pleroma;
|
const { relationship, ...rest } = pleroma;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
|
import DOMPurify from 'isomorphic-dompurify';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import emojify from 'soapbox/features/emoji';
|
import emojify from 'soapbox/features/emoji';
|
||||||
|
@ -30,7 +31,7 @@ const pollSchema = z.object({
|
||||||
|
|
||||||
const emojifiedOptions = poll.options.map((option) => ({
|
const emojifiedOptions = poll.options.map((option) => ({
|
||||||
...option,
|
...option,
|
||||||
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
title_emojified: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(option.title), emojiMap), { ALLOWED_TAGS: [] }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// If the user has votes, they have certainly voted.
|
// If the user has votes, they have certainly voted.
|
||||||
|
|
65
yarn.lock
65
yarn.lock
|
@ -2375,6 +2375,13 @@
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
"@types/responselike" "^1.0.0"
|
"@types/responselike" "^1.0.0"
|
||||||
|
|
||||||
|
"@types/dompurify@^3.0.5":
|
||||||
|
version "3.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.5.tgz#02069a2fcb89a163bacf1a788f73cb415dd75cb7"
|
||||||
|
integrity sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==
|
||||||
|
dependencies:
|
||||||
|
"@types/trusted-types" "*"
|
||||||
|
|
||||||
"@types/escape-html@^1.0.1":
|
"@types/escape-html@^1.0.1":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.1.tgz#b19b4646915f0ae2c306bf984dc0a59c5cfc97ba"
|
resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.1.tgz#b19b4646915f0ae2c306bf984dc0a59c5cfc97ba"
|
||||||
|
@ -2612,6 +2619,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564"
|
||||||
integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==
|
integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==
|
||||||
|
|
||||||
|
"@types/trusted-types@*":
|
||||||
|
version "2.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
|
||||||
|
integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
|
||||||
|
|
||||||
"@types/trusted-types@^2.0.2":
|
"@types/trusted-types@^2.0.2":
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
|
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.4.tgz#2b38784cd16957d3782e8e2b31c03bc1d13b4d65"
|
||||||
|
@ -3885,6 +3897,13 @@ cssstyle@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
rrweb-cssom "^0.6.0"
|
rrweb-cssom "^0.6.0"
|
||||||
|
|
||||||
|
cssstyle@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.0.1.tgz#ef29c598a1e90125c870525490ea4f354db0660a"
|
||||||
|
integrity sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==
|
||||||
|
dependencies:
|
||||||
|
rrweb-cssom "^0.6.0"
|
||||||
|
|
||||||
csstype@^3.0.2:
|
csstype@^3.0.2:
|
||||||
version "3.0.9"
|
version "3.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
|
||||||
|
@ -4101,6 +4120,11 @@ domhandler@^4.2.0, domhandler@^4.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "^2.2.0"
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
|
dompurify@^3.0.8:
|
||||||
|
version "3.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.0.8.tgz#e0021ab1b09184bc8af7e35c7dd9063f43a8a437"
|
||||||
|
integrity sha512-b7uwreMYL2eZhrSCRC4ahLTeZcPZxSmYfmcQGXGkXiZSNW1X85v+SDM5KsWcpivIiUBH47Ji7NtyUdpLeF5JZQ==
|
||||||
|
|
||||||
domutils@^2.8.0:
|
domutils@^2.8.0:
|
||||||
version "2.8.0"
|
version "2.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||||
|
@ -5699,6 +5723,15 @@ isexe@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
||||||
|
|
||||||
|
isomorphic-dompurify@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isomorphic-dompurify/-/isomorphic-dompurify-2.3.0.tgz#bc48fbdf52f84cf7e0a63a5e8ec89052e7dbc3c5"
|
||||||
|
integrity sha512-FCoKY4/mW/jnn/+VgE7wXGC2D/RXzVCAmGYuGWEuZXtyWnwmE2100caciIv+RbHk90q9LA0OW5IBn2f+ywHtww==
|
||||||
|
dependencies:
|
||||||
|
"@types/dompurify" "^3.0.5"
|
||||||
|
dompurify "^3.0.8"
|
||||||
|
jsdom "^24.0.0"
|
||||||
|
|
||||||
iterator.prototype@^1.1.2:
|
iterator.prototype@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0"
|
resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0"
|
||||||
|
@ -5792,6 +5825,33 @@ jsdom@^23.0.0:
|
||||||
ws "^8.14.2"
|
ws "^8.14.2"
|
||||||
xml-name-validator "^5.0.0"
|
xml-name-validator "^5.0.0"
|
||||||
|
|
||||||
|
jsdom@^24.0.0:
|
||||||
|
version "24.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-24.0.0.tgz#e2dc04e4c79da368481659818ee2b0cd7c39007c"
|
||||||
|
integrity sha512-UDS2NayCvmXSXVP6mpTj+73JnNQadZlr9N68189xib2tx5Mls7swlTNao26IoHv46BZJFvXygyRtyXd1feAk1A==
|
||||||
|
dependencies:
|
||||||
|
cssstyle "^4.0.1"
|
||||||
|
data-urls "^5.0.0"
|
||||||
|
decimal.js "^10.4.3"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
html-encoding-sniffer "^4.0.0"
|
||||||
|
http-proxy-agent "^7.0.0"
|
||||||
|
https-proxy-agent "^7.0.2"
|
||||||
|
is-potential-custom-element-name "^1.0.1"
|
||||||
|
nwsapi "^2.2.7"
|
||||||
|
parse5 "^7.1.2"
|
||||||
|
rrweb-cssom "^0.6.0"
|
||||||
|
saxes "^6.0.0"
|
||||||
|
symbol-tree "^3.2.4"
|
||||||
|
tough-cookie "^4.1.3"
|
||||||
|
w3c-xmlserializer "^5.0.0"
|
||||||
|
webidl-conversions "^7.0.0"
|
||||||
|
whatwg-encoding "^3.1.1"
|
||||||
|
whatwg-mimetype "^4.0.0"
|
||||||
|
whatwg-url "^14.0.0"
|
||||||
|
ws "^8.16.0"
|
||||||
|
xml-name-validator "^5.0.0"
|
||||||
|
|
||||||
jsesc@^2.5.1:
|
jsesc@^2.5.1:
|
||||||
version "2.5.2"
|
version "2.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
||||||
|
@ -9483,6 +9543,11 @@ ws@^8.14.2:
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f"
|
||||||
integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
|
integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==
|
||||||
|
|
||||||
|
ws@^8.16.0:
|
||||||
|
version "8.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
|
||||||
|
integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
|
||||||
|
|
||||||
xcase@^2.0.1:
|
xcase@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xcase/-/xcase-2.0.1.tgz#c7fa72caa0f440db78fd5673432038ac984450b9"
|
resolved "https://registry.yarnpkg.com/xcase/-/xcase-2.0.1.tgz#c7fa72caa0f440db78fd5673432038ac984450b9"
|
||||||
|
|
Loading…
Reference in a new issue