Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit bf182bc

Browse files
t3chguyRiotRobot
authored andcommitted
Merge pull request from GHSA-xv83-x443-7rmw
* Escape HTML for plaintext search results * Add tests
1 parent 3b60c80 commit bf182bc

File tree

3 files changed

+55
-9
lines changed

3 files changed

+55
-9
lines changed

src/HtmlUtils.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { decode } from "html-entities";
2828
import { IContent } from "matrix-js-sdk/src/models/event";
2929
import { Optional } from "matrix-events-sdk";
3030
import _Linkify from "linkify-react";
31+
import escapeHtml from "escape-html";
3132

3233
import {
3334
_linkifyElement,
@@ -355,10 +356,10 @@ abstract class BaseHighlighter<T extends React.ReactNode> {
355356
public constructor(public highlightClass: string, public highlightLink?: string) {}
356357

357358
/**
358-
* apply the highlights to a section of text
359+
* Apply the highlights to a section of text
359360
*
360361
* @param {string} safeSnippet The snippet of text to apply the highlights
361-
* to.
362+
* to. This input must be sanitised as it will be treated as HTML.
362363
* @param {string[]} safeHighlights A list of substrings to highlight,
363364
* sorted by descending length.
364365
*
@@ -367,7 +368,7 @@ abstract class BaseHighlighter<T extends React.ReactNode> {
367368
*/
368369
public applyHighlights(safeSnippet: string, safeHighlights: string[]): T[] {
369370
let lastOffset = 0;
370-
let offset;
371+
let offset: number;
371372
let nodes: T[] = [];
372373

373374
const safeHighlight = safeHighlights[0];
@@ -440,7 +441,7 @@ interface IOpts {
440441
}
441442

442443
export interface IOptsReturnNode extends IOpts {
443-
returnString: false | undefined;
444+
returnString?: false | undefined;
444445
}
445446

446447
export interface IOptsReturnString extends IOpts {
@@ -574,7 +575,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
574575
safeBody = formatEmojis(safeBody, true).join("");
575576
}
576577
} else if (highlighter) {
577-
safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
578+
safeBody = highlighter.applyHighlights(escapeHtml(plainBody), safeHighlights!).join("");
578579
}
579580
} finally {
580581
delete sanitizeParams.textFilter;

src/components/views/rooms/SearchResultTile.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default class SearchResultTile extends React.Component<IProps> {
7171

7272
for (let j = 0; j < timeline.length; j++) {
7373
const mxEv = timeline[j];
74-
let highlights;
74+
let highlights: string[] | undefined;
7575
const contextual = !this.props.ourEventsIndexes.includes(j);
7676
if (!contextual) {
7777
highlights = this.props.searchHighlights;

test/HtmlUtils-test.tsx

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import React from "react";
17+
import React, { ReactElement } from "react";
1818
import { mocked } from "jest-mock";
1919
import { render, screen } from "@testing-library/react";
20+
import { IContent } from "matrix-js-sdk/src/models/event";
2021

21-
import { topicToHtml } from "../src/HtmlUtils";
22+
import { bodyToHtml, topicToHtml } from "../src/HtmlUtils";
2223
import SettingsStore from "../src/settings/SettingsStore";
2324

2425
jest.mock("../src/settings/SettingsStore");
@@ -29,7 +30,7 @@ const enableHtmlTopicFeature = () => {
2930
});
3031
};
3132

32-
describe("HtmlUtils", () => {
33+
describe("topicToHtml", () => {
3334
function getContent() {
3435
return screen.getByRole("contentinfo").children[0].innerHTML;
3536
}
@@ -62,3 +63,47 @@ describe("HtmlUtils", () => {
6263
expect(getContent()).toEqual('<b>pizza</b> <span class="mx_Emoji" title=":pizza:">🍕</span>');
6364
});
6465
});
66+
67+
describe("bodyToHtml", () => {
68+
function getHtml(content: IContent, highlights?: string[]): string {
69+
return (bodyToHtml(content, highlights, {}) as ReactElement).props.dangerouslySetInnerHTML.__html;
70+
}
71+
72+
it("should apply highlights to HTML messages", () => {
73+
const html = getHtml(
74+
{
75+
body: "test **foo** bar",
76+
msgtype: "m.text",
77+
formatted_body: "test <b>foo</b> bar",
78+
format: "org.matrix.custom.html",
79+
},
80+
["test"],
81+
);
82+
83+
expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> <b>foo</b> bar"`);
84+
});
85+
86+
it("should apply highlights to plaintext messages", () => {
87+
const html = getHtml(
88+
{
89+
body: "test foo bar",
90+
msgtype: "m.text",
91+
},
92+
["test"],
93+
);
94+
95+
expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo bar"`);
96+
});
97+
98+
it("should not respect HTML tags in plaintext message highlighting", () => {
99+
const html = getHtml(
100+
{
101+
body: "test foo <b>bar",
102+
msgtype: "m.text",
103+
},
104+
["test"],
105+
);
106+
107+
expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo &lt;b&gt;bar"`);
108+
});
109+
});

0 commit comments

Comments
 (0)