Merge pull request from GHSA-xv83-x443-7rmw
* Escape HTML for plaintext search results * Add tests
This commit is contained in:
parent
619a9e8542
commit
961b843662
3 changed files with 55 additions and 9 deletions
|
@ -28,6 +28,7 @@ import { decode } from "html-entities";
|
||||||
import { IContent } from "matrix-js-sdk/src/models/event";
|
import { IContent } from "matrix-js-sdk/src/models/event";
|
||||||
import { Optional } from "matrix-events-sdk";
|
import { Optional } from "matrix-events-sdk";
|
||||||
import _Linkify from "linkify-react";
|
import _Linkify from "linkify-react";
|
||||||
|
import escapeHtml from "escape-html";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
_linkifyElement,
|
_linkifyElement,
|
||||||
|
@ -355,10 +356,10 @@ abstract class BaseHighlighter<T extends React.ReactNode> {
|
||||||
public constructor(public highlightClass: string, public highlightLink?: string) {}
|
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
|
* @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,
|
* @param {string[]} safeHighlights A list of substrings to highlight,
|
||||||
* sorted by descending length.
|
* sorted by descending length.
|
||||||
*
|
*
|
||||||
|
@ -367,7 +368,7 @@ abstract class BaseHighlighter<T extends React.ReactNode> {
|
||||||
*/
|
*/
|
||||||
public applyHighlights(safeSnippet: string, safeHighlights: string[]): T[] {
|
public applyHighlights(safeSnippet: string, safeHighlights: string[]): T[] {
|
||||||
let lastOffset = 0;
|
let lastOffset = 0;
|
||||||
let offset;
|
let offset: number;
|
||||||
let nodes: T[] = [];
|
let nodes: T[] = [];
|
||||||
|
|
||||||
const safeHighlight = safeHighlights[0];
|
const safeHighlight = safeHighlights[0];
|
||||||
|
@ -440,7 +441,7 @@ interface IOpts {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOptsReturnNode extends IOpts {
|
export interface IOptsReturnNode extends IOpts {
|
||||||
returnString: false | undefined;
|
returnString?: false | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOptsReturnString extends IOpts {
|
export interface IOptsReturnString extends IOpts {
|
||||||
|
@ -574,7 +575,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op
|
||||||
safeBody = formatEmojis(safeBody, true).join("");
|
safeBody = formatEmojis(safeBody, true).join("");
|
||||||
}
|
}
|
||||||
} else if (highlighter) {
|
} else if (highlighter) {
|
||||||
safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join("");
|
safeBody = highlighter.applyHighlights(escapeHtml(plainBody), safeHighlights!).join("");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
delete sanitizeParams.textFilter;
|
delete sanitizeParams.textFilter;
|
||||||
|
|
|
@ -71,7 +71,7 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||||
|
|
||||||
for (let j = 0; j < timeline.length; j++) {
|
for (let j = 0; j < timeline.length; j++) {
|
||||||
const mxEv = timeline[j];
|
const mxEv = timeline[j];
|
||||||
let highlights;
|
let highlights: string[] | undefined;
|
||||||
const contextual = !this.props.ourEventsIndexes.includes(j);
|
const contextual = !this.props.ourEventsIndexes.includes(j);
|
||||||
if (!contextual) {
|
if (!contextual) {
|
||||||
highlights = this.props.searchHighlights;
|
highlights = this.props.searchHighlights;
|
||||||
|
|
|
@ -14,11 +14,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React, { ReactElement } from "react";
|
||||||
import { mocked } from "jest-mock";
|
import { mocked } from "jest-mock";
|
||||||
import { render, screen } from "@testing-library/react";
|
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";
|
import SettingsStore from "../src/settings/SettingsStore";
|
||||||
|
|
||||||
jest.mock("../src/settings/SettingsStore");
|
jest.mock("../src/settings/SettingsStore");
|
||||||
|
@ -29,7 +30,7 @@ const enableHtmlTopicFeature = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("HtmlUtils", () => {
|
describe("topicToHtml", () => {
|
||||||
function getContent() {
|
function getContent() {
|
||||||
return screen.getByRole("contentinfo").children[0].innerHTML;
|
return screen.getByRole("contentinfo").children[0].innerHTML;
|
||||||
}
|
}
|
||||||
|
@ -62,3 +63,47 @@ describe("HtmlUtils", () => {
|
||||||
expect(getContent()).toEqual('<b>pizza</b> <span class="mx_Emoji" title=":pizza:">🍕</span>');
|
expect(getContent()).toEqual('<b>pizza</b> <span class="mx_Emoji" title=":pizza:">🍕</span>');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 <b>foo</b> bar",
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
},
|
||||||
|
["test"],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> <b>foo</b> bar"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should apply highlights to plaintext messages", () => {
|
||||||
|
const html = getHtml(
|
||||||
|
{
|
||||||
|
body: "test foo bar",
|
||||||
|
msgtype: "m.text",
|
||||||
|
},
|
||||||
|
["test"],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo bar"`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not respect HTML tags in plaintext message highlighting", () => {
|
||||||
|
const html = getHtml(
|
||||||
|
{
|
||||||
|
body: "test foo <b>bar",
|
||||||
|
msgtype: "m.text",
|
||||||
|
},
|
||||||
|
["test"],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo <b>bar"`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue