@@ -42,6 +42,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
42
42
43
43
let mutableAttributedString = NSMutableAttributedString ( string: string)
44
44
removeDefaultForegroundColors ( mutableAttributedString)
45
+ detectPhishingAttempts ( mutableAttributedString)
45
46
addLinksAndMentions ( mutableAttributedString)
46
47
detectPermalinks ( mutableAttributedString)
47
48
@@ -98,6 +99,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
98
99
99
100
let mutableAttributedString = NSMutableAttributedString ( attributedString: attributedString)
100
101
removeDefaultForegroundColors ( mutableAttributedString)
102
+ detectPhishingAttempts ( mutableAttributedString)
101
103
addLinksAndMentions ( mutableAttributedString)
102
104
replaceMarkedBlockquotes ( mutableAttributedString)
103
105
replaceMarkedCodeBlocks ( mutableAttributedString)
@@ -135,6 +137,22 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
135
137
attributedString. removeAttribute ( . foregroundColor, range: . init( location: 0 , length: attributedString. length) )
136
138
}
137
139
140
+ private func detectPhishingAttempts( _ attributedString: NSMutableAttributedString ) {
141
+ attributedString. enumerateAttribute ( . link, in: . init( location: 0 , length: attributedString. length) , options: [ ] ) { value, range, _ in
142
+ guard value != nil , let internalURL = value as? URL else {
143
+ return
144
+ }
145
+
146
+ var linkString = attributedString. attributedSubstring ( from: range) . string
147
+ guard MatrixEntityRegex . linkRegex. firstMatch ( in: linkString) != nil ,
148
+ linkString != internalURL. absoluteString else {
149
+ return
150
+ }
151
+
152
+ handlePhishingAttempt ( for: attributedString, in: range, internalURL: internalURL, externalURL: linkString)
153
+ }
154
+ }
155
+
138
156
// swiftlint:disable:next cyclomatic_complexity
139
157
private func addLinksAndMentions( _ attributedString: NSMutableAttributedString ) {
140
158
let string = attributedString. string
@@ -177,19 +195,7 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
177
195
return nil
178
196
}
179
197
180
- var link = String ( string [ matchRange] )
181
-
182
- if !link. contains ( " :// " ) {
183
- link. insert ( contentsOf: " https:// " , at: link. startIndex)
184
- }
185
-
186
- // Don't include punctuation characters at the end of links
187
- // e.g `https://element.io/blog:` <- which is a valid link but the wrong place
188
- while !link. isEmpty,
189
- link. rangeOfCharacter ( from: . punctuationCharacters, options: . backwards) ? . upperBound == link. endIndex {
190
- link = String ( link. dropLast ( ) )
191
- }
192
-
198
+ let link = sanitizeLink ( String ( string [ matchRange] ) )
193
199
return TextParsingMatch ( type: . link( urlString: link) , range: match. range)
194
200
} )
195
201
@@ -278,7 +284,8 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
278
284
func detectPermalinks( _ attributedString: NSMutableAttributedString ) {
279
285
attributedString. enumerateAttribute ( . link, in: . init( location: 0 , length: attributedString. length) , options: [ ] ) { value, range, _ in
280
286
if value != nil {
281
- if let url = value as? URL , let matrixEntity = parseMatrixEntityFrom ( uri: url. absoluteString) {
287
+ if let url = value as? URL ,
288
+ let matrixEntity = parseMatrixEntityFrom ( uri: url. absoluteString) {
282
289
switch matrixEntity. id {
283
290
case . user( let userID) :
284
291
mentionBuilder. handleUserMention ( for: attributedString, in: range, url: url, userID: userID, userDisplayName: nil )
@@ -303,6 +310,42 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
303
310
}
304
311
}
305
312
313
+ private func sanitizeLink( _ string: String ) -> String {
314
+ var link = string
315
+ if !link. contains ( " :// " ) {
316
+ link. insert ( contentsOf: " https:// " , at: link. startIndex)
317
+ }
318
+
319
+ // Don't include punctuation characters at the end of links
320
+ // e.g `https://element.io/blog:` <- which is a valid link but the wrong place
321
+ while !link. isEmpty,
322
+ link. rangeOfCharacter ( from: . punctuationCharacters, options: . backwards) ? . upperBound == link. endIndex {
323
+ link = String ( link. dropLast ( ) )
324
+ }
325
+
326
+ return link
327
+ }
328
+
329
+ private func handlePhishingAttempt( for attributedString: NSMutableAttributedString ,
330
+ in range: NSRange ,
331
+ internalURL: URL ,
332
+ externalURL: String ) {
333
+ // Let's remove the existing link attribute
334
+ attributedString. removeAttribute ( . link, range: range)
335
+
336
+ var urlComponents = URLComponents ( )
337
+ urlComponents. scheme = URL . confirmationScheme
338
+ urlComponents. host = " "
339
+ let parameters = ConfirmURLParameters ( internalURL: internalURL, externalURL: externalURL)
340
+ urlComponents. queryItems = parameters. urlQueryItems
341
+
342
+ guard let finalURL = urlComponents. url else {
343
+ return
344
+ }
345
+
346
+ attributedString. addAttribute ( . link, value: finalURL, range: range)
347
+ }
348
+
306
349
private func removeDTCoreTextArtifacts( _ attributedString: NSMutableAttributedString ) {
307
350
guard attributedString. length > 0 else {
308
351
return
0 commit comments