@@ -17,32 +17,25 @@ See the License for the specific language governing permissions and
17
17
limitations under the License.
18
18
*/
19
19
20
- import React , { LegacyRef , ReactElement , ReactNode } from "react" ;
20
+ import React , { LegacyRef , ReactNode } from "react" ;
21
21
import sanitizeHtml from "sanitize-html" ;
22
22
import classNames from "classnames" ;
23
23
import EMOJIBASE_REGEX from "emojibase-regex" ;
24
- import { merge } from "lodash" ;
25
24
import katex from "katex" ;
26
25
import { decode } from "html-entities" ;
27
26
import { IContent } from "matrix-js-sdk/src/matrix" ;
28
27
import { Optional } from "matrix-events-sdk" ;
29
- import _Linkify from "linkify-react" ;
30
28
import escapeHtml from "escape-html" ;
31
29
import GraphemeSplitter from "graphemer" ;
32
30
import { getEmojiFromUnicode } from "@matrix-org/emojibase-bindings" ;
33
31
34
- import {
35
- _linkifyElement ,
36
- _linkifyString ,
37
- ELEMENT_URL_PATTERN ,
38
- options as linkifyMatrixOptions ,
39
- } from "./linkify-matrix" ;
40
32
import { IExtendedSanitizeOptions } from "./@types/sanitize-html" ;
41
33
import SettingsStore from "./settings/SettingsStore" ;
42
- import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks" ;
43
- import { mediaFromMxc } from "./customisations/Media" ;
44
34
import { stripHTMLReply , stripPlainReply } from "./utils/Reply" ;
45
35
import { PERMITTED_URL_SCHEMES } from "./utils/UrlUtils" ;
36
+ import { sanitizeHtmlParams , transformTags } from "./Linkify" ;
37
+
38
+ export { Linkify , linkifyElement , linkifyAndSanitizeHtml } from "./Linkify" ;
46
39
47
40
// Anything outside the basic multilingual plane will be a surrogate pair
48
41
const SURROGATE_PAIR_PATTERN = / ( [ \ud800 - \udbff ] ) ( [ \udc00 - \udfff ] ) / ;
@@ -58,10 +51,6 @@ const EMOJI_SEPARATOR_REGEX = /[\u200D\u200B\s]|\uFE0F/g;
58
51
59
52
const BIGEMOJI_REGEX = new RegExp ( `^(${ EMOJIBASE_REGEX . source } )+$` , "i" ) ;
60
53
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
-
65
54
/*
66
55
* Return true if the given string contains emoji
67
56
* Uses a much, much simpler regex than emojibase's so will give false
@@ -120,182 +109,6 @@ export function isUrlPermitted(inputUrl: string): boolean {
120
109
}
121
110
}
122
111
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
-
299
112
// this is the same as the above except with less rewriting
300
113
const composerSanitizeHtmlParams : IExtendedSanitizeOptions = {
301
114
...sanitizeHtmlParams ,
@@ -657,48 +470,6 @@ export function topicToHtml(
657
470
) ;
658
471
}
659
472
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
-
702
473
/**
703
474
* Returns if a node is a block element or not.
704
475
* Only takes html nodes into account that are allowed in matrix messages.
0 commit comments