Update html utils to properly discard media.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2024 New Vector Ltd.
|
Copyright 2024, 2025 New Vector Ltd.
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2017, 2018 New Vector Ltd
|
Copyright 2017, 2018 New Vector Ltd
|
||||||
@@ -22,7 +22,7 @@ import { getEmojiFromUnicode } from "@matrix-org/emojibase-bindings";
|
|||||||
import SettingsStore from "./settings/SettingsStore";
|
import SettingsStore from "./settings/SettingsStore";
|
||||||
import { stripHTMLReply, stripPlainReply } from "./utils/Reply";
|
import { stripHTMLReply, stripPlainReply } from "./utils/Reply";
|
||||||
import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils";
|
import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils";
|
||||||
import { filterImg, sanitizeHtmlParams, transformTags } from "./Linkify";
|
import { sanitizeHtmlParams, transformTags } from "./Linkify";
|
||||||
import { graphemeSegmenter } from "./utils/strings";
|
import { graphemeSegmenter } from "./utils/strings";
|
||||||
|
|
||||||
export { Linkify, linkifyAndSanitizeHtml } from "./Linkify";
|
export { Linkify, linkifyAndSanitizeHtml } from "./Linkify";
|
||||||
@@ -302,8 +302,15 @@ function analyseEvent(content: IContent, highlights: Optional<string[]>, opts: E
|
|||||||
if (opts.forComposerQuote) {
|
if (opts.forComposerQuote) {
|
||||||
sanitizeParams = composerSanitizeHtmlParams;
|
sanitizeParams = composerSanitizeHtmlParams;
|
||||||
}
|
}
|
||||||
if (!opts.mediaIsVisible) {
|
|
||||||
sanitizeParams.exclusiveFilter = filterImg;
|
if (opts.mediaIsVisible === false && sanitizeParams.transformTags?.["img"]) {
|
||||||
|
// Prevent mutating the source of sanitizeParams.
|
||||||
|
sanitizeParams.transformTags = {
|
||||||
|
...sanitizeParams.transformTags,
|
||||||
|
img: (tagName) => {
|
||||||
|
return { tagName, attribs: {} };
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { type ReactElement } from "react";
|
import React, { type ReactElement } from "react";
|
||||||
import sanitizeHtml, { IFrame, type IOptions } from "sanitize-html";
|
import sanitizeHtml, { type IOptions } from "sanitize-html";
|
||||||
import { merge } from "lodash";
|
import { merge } from "lodash";
|
||||||
import _Linkify from "linkify-react";
|
import _Linkify from "linkify-react";
|
||||||
|
|
||||||
@@ -46,8 +46,6 @@ export const transformTags: NonNullable<IOptions["transformTags"]> = {
|
|||||||
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
|
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
|
||||||
// because transformTags is used _before_ we filter by allowedSchemesByTag and
|
// because transformTags is used _before_ we filter by allowedSchemesByTag and
|
||||||
// we don't want to allow images with `https?` `src`s.
|
// we don't want to allow images with `https?` `src`s.
|
||||||
// Filtering out images now happens as a exlusive filter so we can conditionally apply this
|
|
||||||
// based on settings.
|
|
||||||
if (!src) {
|
if (!src) {
|
||||||
return { tagName, attribs: {} };
|
return { tagName, attribs: {} };
|
||||||
}
|
}
|
||||||
@@ -76,7 +74,6 @@ export const transformTags: NonNullable<IOptions["transformTags"]> = {
|
|||||||
if (requestedHeight) {
|
if (requestedHeight) {
|
||||||
attribs.style += "height: 100%;";
|
attribs.style += "height: 100%;";
|
||||||
}
|
}
|
||||||
|
|
||||||
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
|
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
|
||||||
return { tagName, attribs };
|
return { tagName, attribs };
|
||||||
},
|
},
|
||||||
@@ -196,7 +193,6 @@ export const sanitizeHtmlParams: IOptions = {
|
|||||||
nestingLimit: 50,
|
nestingLimit: 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/* Wrapper around linkify-react merging in our default linkify options */
|
/* Wrapper around linkify-react merging in our default linkify options */
|
||||||
export function Linkify({ as, options, children }: React.ComponentProps<typeof _Linkify>): ReactElement {
|
export function Linkify({ as, options, children }: React.ComponentProps<typeof _Linkify>): ReactElement {
|
||||||
return (
|
return (
|
||||||
@@ -227,7 +223,3 @@ export function linkifyString(str: string, options = linkifyMatrixOptions): stri
|
|||||||
export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrixOptions): string {
|
export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrixOptions): string {
|
||||||
return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams);
|
return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function filterImg(frame: IFrame): boolean {
|
|
||||||
return frame.tag === "img";
|
|
||||||
}
|
|
||||||
@@ -13,6 +13,7 @@ import parse from "html-react-parser";
|
|||||||
|
|
||||||
import { bodyToHtml, bodyToNode, formatEmojis, topicToHtml } from "../../src/HtmlUtils";
|
import { bodyToHtml, bodyToNode, formatEmojis, topicToHtml } from "../../src/HtmlUtils";
|
||||||
import SettingsStore from "../../src/settings/SettingsStore";
|
import SettingsStore from "../../src/settings/SettingsStore";
|
||||||
|
import { getMockClientWithEventEmitter } from "../test-utils";
|
||||||
|
|
||||||
jest.mock("../../src/settings/SettingsStore");
|
jest.mock("../../src/settings/SettingsStore");
|
||||||
|
|
||||||
@@ -228,4 +229,39 @@ describe("bodyToNode", () => {
|
|||||||
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([[true], [false]])("should handle inline media when mediaIsVisible is %s", (mediaIsVisible) => {
|
||||||
|
const cli = getMockClientWithEventEmitter({
|
||||||
|
mxcUrlToHttp: jest.fn().mockReturnValue("https://example.org/img"),
|
||||||
|
});
|
||||||
|
const { className, formattedBody } = bodyToNode(
|
||||||
|
{
|
||||||
|
"body": " Hello there",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": `<img src="mxc://going/knowwhere">foo</img> Hello there`,
|
||||||
|
"m.relates_to": {
|
||||||
|
"m.in_reply_to": {
|
||||||
|
event_id: "$eventId",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"msgtype": "m.text",
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
mediaIsVisible,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { asFragment } = render(
|
||||||
|
<span className={className} dir="auto" dangerouslySetInnerHTML={{ __html: formattedBody! }} />,
|
||||||
|
);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
// We do not want to download untrusted media.
|
||||||
|
// eslint-disable-next-line no-restricted-properties
|
||||||
|
expect(cli.mxcUrlToHttp).toHaveBeenCalledTimes(mediaIsVisible ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -64,3 +64,30 @@ exports[`bodyToNode should generate big emoji for an emoji-only reply to a messa
|
|||||||
</span>
|
</span>
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`bodyToNode should handle inline media when mediaIsVisible is false 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<span
|
||||||
|
class="mx_EventTile_body markdown-body translate"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
|
<img />
|
||||||
|
foo Hello there
|
||||||
|
</span>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`bodyToNode should handle inline media when mediaIsVisible is true 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<span
|
||||||
|
class="mx_EventTile_body markdown-body translate"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://example.org/img"
|
||||||
|
style="max-width:800px;max-height:600px"
|
||||||
|
/>
|
||||||
|
foo Hello there
|
||||||
|
</span>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user