diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx
index f925facaf0..71f3c9e079 100644
--- a/src/HtmlUtils.tsx
+++ b/src/HtmlUtils.tsx
@@ -28,6 +28,7 @@ import { decode } from "html-entities";
import { IContent } from "matrix-js-sdk/src/models/event";
import { Optional } from "matrix-events-sdk";
import _Linkify from "linkify-react";
+import escapeHtml from "escape-html";
import {
_linkifyElement,
@@ -355,10 +356,10 @@ abstract class BaseHighlighter {
public constructor(public highlightClass: string, public highlightLink?: string) {}
/**
- * apply the highlights to a section of text
+ * Apply the highlights to a section of text
*
* @param {string} safeSnippet The snippet of text to apply the highlights
- * to.
+ * to. This input must be sanitised as it will be treated as HTML.
* @param {string[]} safeHighlights A list of substrings to highlight,
* sorted by descending length.
*
@@ -367,7 +368,7 @@ abstract class BaseHighlighter {
*/
public applyHighlights(safeSnippet: string, safeHighlights: string[]): T[] {
let lastOffset = 0;
- let offset;
+ let offset: number;
let nodes: T[] = [];
const safeHighlight = safeHighlights[0];
@@ -440,7 +441,7 @@ interface IOpts {
}
export interface IOptsReturnNode extends IOpts {
- returnString: false | undefined;
+ returnString?: false | undefined;
}
export interface IOptsReturnString extends IOpts {
@@ -574,7 +575,7 @@ export function bodyToHtml(content: IContent, highlights: Optional, op
safeBody = formatEmojis(safeBody, true).join("");
}
} else if (highlighter) {
- safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
+ safeBody = highlighter.applyHighlights(escapeHtml(plainBody), safeHighlights!).join("");
}
} finally {
delete sanitizeParams.textFilter;
diff --git a/src/components/views/rooms/SearchResultTile.tsx b/src/components/views/rooms/SearchResultTile.tsx
index be15ea9694..6ddb7f1067 100644
--- a/src/components/views/rooms/SearchResultTile.tsx
+++ b/src/components/views/rooms/SearchResultTile.tsx
@@ -71,7 +71,7 @@ export default class SearchResultTile extends React.Component {
for (let j = 0; j < timeline.length; j++) {
const mxEv = timeline[j];
- let highlights;
+ let highlights: string[] | undefined;
const contextual = !this.props.ourEventsIndexes.includes(j);
if (!contextual) {
highlights = this.props.searchHighlights;
diff --git a/test/HtmlUtils-test.tsx b/test/HtmlUtils-test.tsx
index 8baeea446a..d63470e122 100644
--- a/test/HtmlUtils-test.tsx
+++ b/test/HtmlUtils-test.tsx
@@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React from "react";
+import React, { ReactElement } from "react";
import { mocked } from "jest-mock";
import { render, screen } from "@testing-library/react";
+import { IContent } from "matrix-js-sdk/src/models/event";
-import { topicToHtml } from "../src/HtmlUtils";
+import { bodyToHtml, topicToHtml } from "../src/HtmlUtils";
import SettingsStore from "../src/settings/SettingsStore";
jest.mock("../src/settings/SettingsStore");
@@ -29,7 +30,7 @@ const enableHtmlTopicFeature = () => {
});
};
-describe("HtmlUtils", () => {
+describe("topicToHtml", () => {
function getContent() {
return screen.getByRole("contentinfo").children[0].innerHTML;
}
@@ -62,3 +63,47 @@ describe("HtmlUtils", () => {
expect(getContent()).toEqual('pizza 🍕');
});
});
+
+describe("bodyToHtml", () => {
+ function getHtml(content: IContent, highlights?: string[]): string {
+ return (bodyToHtml(content, highlights, {}) as ReactElement).props.dangerouslySetInnerHTML.__html;
+ }
+
+ it("should apply highlights to HTML messages", () => {
+ const html = getHtml(
+ {
+ body: "test **foo** bar",
+ msgtype: "m.text",
+ formatted_body: "test foo bar",
+ format: "org.matrix.custom.html",
+ },
+ ["test"],
+ );
+
+ expect(html).toMatchInlineSnapshot(`"test foo bar"`);
+ });
+
+ it("should apply highlights to plaintext messages", () => {
+ const html = getHtml(
+ {
+ body: "test foo bar",
+ msgtype: "m.text",
+ },
+ ["test"],
+ );
+
+ expect(html).toMatchInlineSnapshot(`"test foo bar"`);
+ });
+
+ it("should not respect HTML tags in plaintext message highlighting", () => {
+ const html = getHtml(
+ {
+ body: "test foo bar",
+ msgtype: "m.text",
+ },
+ ["test"],
+ );
+
+ expect(html).toMatchInlineSnapshot(`"test foo <b>bar"`);
+ });
+});