@@ -17,32 +17,25 @@ See the License for the specific language governing permissions and
1717limitations under the License.
1818*/
1919
20- import React , { LegacyRef , ReactElement , ReactNode } from "react" ;
20+ import React , { LegacyRef , ReactNode } from "react" ;
2121import sanitizeHtml from "sanitize-html" ;
2222import classNames from "classnames" ;
2323import EMOJIBASE_REGEX from "emojibase-regex" ;
24- import { merge } from "lodash" ;
2524import katex from "katex" ;
2625import { decode } from "html-entities" ;
2726import { IContent } from "matrix-js-sdk/src/matrix" ;
2827import { Optional } from "matrix-events-sdk" ;
29- import _Linkify from "linkify-react" ;
3028import escapeHtml from "escape-html" ;
3129import GraphemeSplitter from "graphemer" ;
3230import { 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" ;
4032import { IExtendedSanitizeOptions } from "./@types/sanitize-html" ;
4133import SettingsStore from "./settings/SettingsStore" ;
42- import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks" ;
43- import { mediaFromMxc } from "./customisations/Media" ;
4434import { stripHTMLReply , stripPlainReply } from "./utils/Reply" ;
4535import { 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
4841const SURROGATE_PAIR_PATTERN = / ( [ \ud800 - \udbff ] ) ( [ \udc00 - \udfff ] ) / ;
@@ -58,10 +51,6 @@ const EMOJI_SEPARATOR_REGEX = /[\u200D\u200B\s]|\uFE0F/g;
5851
5952const BIGEMOJI_REGEX = new RegExp ( `^(${ EMOJIBASE_REGEX . source } )+$` , "i" ) ;
6053
61- const COLOR_REGEX = / ^ # [ 0 - 9 a - f A - F ] { 6 } $ / ;
62-
63- const MEDIA_API_MXC_REGEX = / \/ _ m a t r i x \/ m e d i a \/ r 0 \/ (?: d o w n l o a d | t h u m b n a i l ) \/ ( .+ ?) \/ ( .+ ?) (?: [ ? / ] | $ ) / ;
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
300113const 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