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

Commit 2760f9d

Browse files
authored
Linkify User Interactive Authentication errors (#12271)
1 parent 5ed68ef commit 2760f9d

File tree

3 files changed

+261
-234
lines changed

3 files changed

+261
-234
lines changed

src/HtmlUtils.tsx

Lines changed: 4 additions & 233 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,25 @@ See the License for the specific language governing permissions and
1717
limitations under the License.
1818
*/
1919

20-
import React, { LegacyRef, ReactElement, ReactNode } from "react";
20+
import React, { LegacyRef, ReactNode } from "react";
2121
import sanitizeHtml from "sanitize-html";
2222
import classNames from "classnames";
2323
import EMOJIBASE_REGEX from "emojibase-regex";
24-
import { merge } from "lodash";
2524
import katex from "katex";
2625
import { decode } from "html-entities";
2726
import { IContent } from "matrix-js-sdk/src/matrix";
2827
import { Optional } from "matrix-events-sdk";
29-
import _Linkify from "linkify-react";
3028
import escapeHtml from "escape-html";
3129
import GraphemeSplitter from "graphemer";
3230
import { getEmojiFromUnicode } from "@matrix-org/emojibase-bindings";
3331

34-
import {
35-
_linkifyElement,
36-
_linkifyString,
37-
ELEMENT_URL_PATTERN,
38-
options as linkifyMatrixOptions,
39-
} from "./linkify-matrix";
4032
import { IExtendedSanitizeOptions } from "./@types/sanitize-html";
4133
import SettingsStore from "./settings/SettingsStore";
42-
import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks";
43-
import { mediaFromMxc } from "./customisations/Media";
4434
import { stripHTMLReply, stripPlainReply } from "./utils/Reply";
4535
import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils";
36+
import { sanitizeHtmlParams, transformTags } from "./Linkify";
37+
38+
export { Linkify, linkifyElement, linkifyAndSanitizeHtml } from "./Linkify";
4639

4740
// Anything outside the basic multilingual plane will be a surrogate pair
4841
const SURROGATE_PAIR_PATTERN = /([\ud800-\udbff])([\udc00-\udfff])/;
@@ -58,10 +51,6 @@ const EMOJI_SEPARATOR_REGEX = /[\u200D\u200B\s]|\uFE0F/g;
5851

5952
const BIGEMOJI_REGEX = new RegExp(`^(${EMOJIBASE_REGEX.source})+$`, "i");
6053

61-
const COLOR_REGEX = /^#[0-9a-fA-F]{6}$/;
62-
63-
const MEDIA_API_MXC_REGEX = /\/_matrix\/media\/r0\/(?:download|thumbnail)\/(.+?)\/(.+?)(?:[?/]|$)/;
64-
6554
/*
6655
* Return true if the given string contains emoji
6756
* Uses a much, much simpler regex than emojibase's so will give false
@@ -120,182 +109,6 @@ export function isUrlPermitted(inputUrl: string): boolean {
120109
}
121110
}
122111

123-
const transformTags: IExtendedSanitizeOptions["transformTags"] = {
124-
// custom to matrix
125-
// add blank targets to all hyperlinks except vector URLs
126-
"a": function (tagName: string, attribs: sanitizeHtml.Attributes) {
127-
if (attribs.href) {
128-
attribs.target = "_blank"; // by default
129-
130-
const transformed = tryTransformPermalinkToLocalHref(attribs.href); // only used to check if it is a link that can be handled locally
131-
if (
132-
transformed !== attribs.href || // it could be converted so handle locally symbols e.g. @user:server.tdl, matrix: and matrix.to
133-
attribs.href.match(ELEMENT_URL_PATTERN) // for https links to Element domains
134-
) {
135-
delete attribs.target;
136-
}
137-
} else {
138-
// Delete the href attrib if it is falsy
139-
delete attribs.href;
140-
}
141-
142-
attribs.rel = "noreferrer noopener"; // https://mathiasbynens.github.io/rel-noopener/
143-
return { tagName, attribs };
144-
},
145-
"img": function (tagName: string, attribs: sanitizeHtml.Attributes) {
146-
let src = attribs.src;
147-
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
148-
// because transformTags is used _before_ we filter by allowedSchemesByTag and
149-
// we don't want to allow images with `https?` `src`s.
150-
// We also drop inline images (as if they were not present at all) when the "show
151-
// images" preference is disabled. Future work might expose some UI to reveal them
152-
// like standalone image events have.
153-
if (!src || !SettingsStore.getValue("showImages")) {
154-
return { tagName, attribs: {} };
155-
}
156-
157-
if (!src.startsWith("mxc://")) {
158-
const match = MEDIA_API_MXC_REGEX.exec(src);
159-
if (match) {
160-
src = `mxc://${match[1]}/${match[2]}`;
161-
}
162-
}
163-
164-
if (!src.startsWith("mxc://")) {
165-
return { tagName, attribs: {} };
166-
}
167-
168-
const requestedWidth = Number(attribs.width);
169-
const requestedHeight = Number(attribs.height);
170-
const width = Math.min(requestedWidth || 800, 800);
171-
const height = Math.min(requestedHeight || 600, 600);
172-
// specify width/height as max values instead of absolute ones to allow object-fit to do its thing
173-
// we only allow our own styles for this tag so overwrite the attribute
174-
attribs.style = `max-width: ${width}px; max-height: ${height}px;`;
175-
if (requestedWidth) {
176-
attribs.style += "width: 100%;";
177-
}
178-
if (requestedHeight) {
179-
attribs.style += "height: 100%;";
180-
}
181-
182-
attribs.src = mediaFromMxc(src).getThumbnailOfSourceHttp(width, height)!;
183-
return { tagName, attribs };
184-
},
185-
"code": function (tagName: string, attribs: sanitizeHtml.Attributes) {
186-
if (typeof attribs.class !== "undefined") {
187-
// Filter out all classes other than ones starting with language- for syntax highlighting.
188-
const classes = attribs.class.split(/\s/).filter(function (cl) {
189-
return cl.startsWith("language-") && !cl.startsWith("language-_");
190-
});
191-
attribs.class = classes.join(" ");
192-
}
193-
return { tagName, attribs };
194-
},
195-
// eslint-disable-next-line @typescript-eslint/naming-convention
196-
"*": function (tagName: string, attribs: sanitizeHtml.Attributes) {
197-
// Delete any style previously assigned, style is an allowedTag for font, span & img,
198-
// because attributes are stripped after transforming.
199-
// For img this is trusted as it is generated wholly within the img transformation method.
200-
if (tagName !== "img") {
201-
delete attribs.style;
202-
}
203-
204-
// Sanitise and transform data-mx-color and data-mx-bg-color to their CSS
205-
// equivalents
206-
const customCSSMapper: Record<string, string> = {
207-
"data-mx-color": "color",
208-
"data-mx-bg-color": "background-color",
209-
// $customAttributeKey: $cssAttributeKey
210-
};
211-
212-
let style = "";
213-
Object.keys(customCSSMapper).forEach((customAttributeKey) => {
214-
const cssAttributeKey = customCSSMapper[customAttributeKey];
215-
const customAttributeValue = attribs[customAttributeKey];
216-
if (
217-
customAttributeValue &&
218-
typeof customAttributeValue === "string" &&
219-
COLOR_REGEX.test(customAttributeValue)
220-
) {
221-
style += cssAttributeKey + ":" + customAttributeValue + ";";
222-
delete attribs[customAttributeKey];
223-
}
224-
});
225-
226-
if (style) {
227-
attribs.style = style + (attribs.style || "");
228-
}
229-
230-
return { tagName, attribs };
231-
},
232-
};
233-
234-
const sanitizeHtmlParams: IExtendedSanitizeOptions = {
235-
allowedTags: [
236-
"font", // custom to matrix for IRC-style font coloring
237-
"del", // for markdown
238-
"h1",
239-
"h2",
240-
"h3",
241-
"h4",
242-
"h5",
243-
"h6",
244-
"blockquote",
245-
"p",
246-
"a",
247-
"ul",
248-
"ol",
249-
"sup",
250-
"sub",
251-
"nl",
252-
"li",
253-
"b",
254-
"i",
255-
"u",
256-
"strong",
257-
"em",
258-
"strike",
259-
"code",
260-
"hr",
261-
"br",
262-
"div",
263-
"table",
264-
"thead",
265-
"caption",
266-
"tbody",
267-
"tr",
268-
"th",
269-
"td",
270-
"pre",
271-
"span",
272-
"img",
273-
"details",
274-
"summary",
275-
],
276-
allowedAttributes: {
277-
// attribute sanitization happens after transformations, so we have to accept `style` for font, span & img
278-
// but strip during the transformation.
279-
// custom ones first:
280-
font: ["color", "data-mx-bg-color", "data-mx-color", "style"], // custom to matrix
281-
span: ["data-mx-maths", "data-mx-bg-color", "data-mx-color", "data-mx-spoiler", "style"], // custom to matrix
282-
div: ["data-mx-maths"],
283-
a: ["href", "name", "target", "rel"], // remote target: custom to matrix
284-
// img tags also accept width/height, we just map those to max-width & max-height during transformation
285-
img: ["src", "alt", "title", "style"],
286-
ol: ["start"],
287-
code: ["class"], // We don't actually allow all classes, we filter them in transformTags
288-
},
289-
// Lots of these won't come up by default because we don't allow them
290-
selfClosing: ["img", "br", "hr", "area", "base", "basefont", "input", "link", "meta"],
291-
// URL schemes we permit
292-
allowedSchemes: PERMITTED_URL_SCHEMES,
293-
allowProtocolRelative: false,
294-
transformTags,
295-
// 50 levels deep "should be enough for anyone"
296-
nestingLimit: 50,
297-
};
298-
299112
// this is the same as the above except with less rewriting
300113
const composerSanitizeHtmlParams: IExtendedSanitizeOptions = {
301114
...sanitizeHtmlParams,
@@ -657,48 +470,6 @@ export function topicToHtml(
657470
);
658471
}
659472

660-
/* Wrapper around linkify-react merging in our default linkify options */
661-
export function Linkify({ as, options, children }: React.ComponentProps<typeof _Linkify>): ReactElement {
662-
return (
663-
<_Linkify as={as} options={merge({}, linkifyMatrixOptions, options)}>
664-
{children}
665-
</_Linkify>
666-
);
667-
}
668-
669-
/**
670-
* Linkifies the given string. This is a wrapper around 'linkifyjs/string'.
671-
*
672-
* @param {string} str string to linkify
673-
* @param {object} [options] Options for linkifyString. Default: linkifyMatrixOptions
674-
* @returns {string} Linkified string
675-
*/
676-
export function linkifyString(str: string, options = linkifyMatrixOptions): string {
677-
return _linkifyString(str, options);
678-
}
679-
680-
/**
681-
* Linkifies the given DOM element. This is a wrapper around 'linkifyjs/element'.
682-
*
683-
* @param {object} element DOM element to linkify
684-
* @param {object} [options] Options for linkifyElement. Default: linkifyMatrixOptions
685-
* @returns {object}
686-
*/
687-
export function linkifyElement(element: HTMLElement, options = linkifyMatrixOptions): HTMLElement {
688-
return _linkifyElement(element, options);
689-
}
690-
691-
/**
692-
* Linkify the given string and sanitize the HTML afterwards.
693-
*
694-
* @param {string} dirtyHtml The HTML string to sanitize and linkify
695-
* @param {object} [options] Options for linkifyString. Default: linkifyMatrixOptions
696-
* @returns {string}
697-
*/
698-
export function linkifyAndSanitizeHtml(dirtyHtml: string, options = linkifyMatrixOptions): string {
699-
return sanitizeHtml(linkifyString(dirtyHtml, options), sanitizeHtmlParams);
700-
}
701-
702473
/**
703474
* Returns if a node is a block element or not.
704475
* Only takes html nodes into account that are allowed in matrix messages.

0 commit comments

Comments
 (0)