Skip to content

Commit

Permalink
Fixes #464 - Incorrect regex usage on extended grapheme clusters
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanceriu committed Feb 10, 2023
1 parent 910a8e2 commit bb67b92
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 20 deletions.
20 changes: 16 additions & 4 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,6 @@
A663FE6704CB500EBE782AE1 /* AnalyticsPromptCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4DE1CF8F5EFD353B1A5E36F /* AnalyticsPromptCoordinator.swift */; };
A69A54FF11A3F9EA0660E6BF /* NSE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0D8F620C8B314840D8602E3F /* NSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; };
A7CC2102298ACB1700DBE1C7 /* ProgressTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7CC2101298ACB1700DBE1C7 /* ProgressTracker.swift */; };
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; };
A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; };
A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; };
Expand Down Expand Up @@ -468,6 +467,7 @@
E89536FC8C0E4B79E9842A78 /* RoomTimelineControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C0197EAE9D45A662B8847B6 /* RoomTimelineControllerProtocol.swift */; };
E8AB8D16E6D8E8E501F29BD9 /* FileCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5B0B1226DA8DB55918B34CD /* FileCache.swift */; };
E96005321849DBD7C72A28F2 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46C208DA43CE25D13E670F40 /* UITestsAppCoordinator.swift */; };
EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; };
EA1E7949533E19C6D862680A /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 885D8C42DD17625B5261BEFF /* MediaProvider.swift */; };
EA31DD9043B91ECB8E45A9A6 /* ScreenshotDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */; };
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; };
Expand All @@ -493,6 +493,7 @@
F32B271F60531BE92C6E62A1 /* StickerRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612EF972F2A1800682D32C5E /* StickerRoomTimelineView.swift */; };
F425C3F85BFF28C9AC593F52 /* MockNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96561CC53F7C1E24D4C292E4 /* MockNotificationManager.swift */; };
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
F587A9AF25A262DE5A7B0369 /* ProgressTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28551E81CE3700E5F1EC9B5 /* ProgressTracker.swift */; };
F61AFA8BF2E739FBC30472F5 /* NotificationServiceProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD5FEE195446A9E458DDDAF /* NotificationServiceProxyProtocol.swift */; };
F656F92A63D3DC1978D79427 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; };
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; };
Expand Down Expand Up @@ -864,6 +865,7 @@
93B21E72926FACB13A186689 /* ml */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ml; path = ml.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelTests.swift; sourceTree = "<group>"; };
94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemPlainStylerView.swift; sourceTree = "<group>"; };
95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSRegularExpresion.swift; sourceTree = "<group>"; };
96561CC53F7C1E24D4C292E4 /* MockNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNotificationManager.swift; sourceTree = "<group>"; };
96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomStringConvertible.swift; sourceTree = "<group>"; };
9772C1D2223108EB3131AEE4 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -907,7 +909,6 @@
A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomProxy.swift; sourceTree = "<group>"; };
A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = "<group>"; };
A72232816DCE2B76D48E1367 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "nb-NO"; path = "nb-NO.lproj/Localizable.strings"; sourceTree = "<group>"; };
A7CC2101298ACB1700DBE1C7 /* ProgressTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressTracker.swift; sourceTree = "<group>"; };
A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = "<group>"; };
A8F48EB9B52E70285A4BCB07 /* ur */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ur; path = ur.lproj/Localizable.strings; sourceTree = "<group>"; };
A9873374E72AA53260AE90A2 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/Localizable.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1086,6 +1087,7 @@
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = "<group>"; };
F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
F28551E81CE3700E5F1EC9B5 /* ProgressTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressTracker.swift; sourceTree = "<group>"; };
F2D58333B377888012740101 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
F31A4E5941ACBA4BB9FEF94C /* UserNotificationToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationToastView.swift; sourceTree = "<group>"; };
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1478,7 +1480,12 @@
E26747B3154A5DBC3A7E24A5 /* Image.swift */,
4E2245243369B99216C7D84E /* ImageCache.swift */,
2AFEF3AC64B1358083F76B8B /* List.swift */,
<<<<<<< HEAD
7310D8DFE01AF45F0689C3AA /* Publisher.swift */,
||||||| parent of 829ba5a (Fixes #464 - Incorrect regex usage on extended grapheme clusters)
=======
95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */,
>>>>>>> 829ba5a (Fixes #464 - Incorrect regex usage on extended grapheme clusters)
40B21E611DADDEF00307E7AC /* String.swift */,
A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */,
A40C19719687984FD9478FBE /* Task.swift */,
Expand Down Expand Up @@ -2265,7 +2272,7 @@
6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */,
C789E7BFC066CF39B8AE0974 /* NetworkMonitor.swift */,
F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */,
A7CC2101298ACB1700DBE1C7 /* ProgressTracker.swift */,
F28551E81CE3700E5F1EC9B5 /* ProgressTracker.swift */,
53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */,
BB3073CCD77D906B330BC1D6 /* Tests.swift */,
1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */,
Expand Down Expand Up @@ -3105,7 +3112,6 @@
B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */,
02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */,
33D630461FC4562CC767EE9F /* FileCache.swift in Sources */,
A7CC2102298ACB1700DBE1C7 /* ProgressTracker.swift in Sources */,
5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */,
6C67774E8387D44426718BD9 /* FilePreviewCoordinator.swift in Sources */,
6C9F6C7F2B35288C4230EF3F /* FilePreviewModels.swift in Sources */,
Expand Down Expand Up @@ -3171,6 +3177,7 @@
C74EE50257ED925C2B8EFCE6 /* MockSoftLogoutScreenState.swift in Sources */,
FE8D76708280968F7A670852 /* MockUserNotificationController.swift in Sources */,
D8359F67AF3A83516E9083C1 /* MockUserSession.swift in Sources */,
EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */,
FA2BBAE9FC5E2E9F960C0980 /* NavigationCoordinators.swift in Sources */,
71C1347F23868324A4F43940 /* NavigationModule.swift in Sources */,
B5E455C9689EA600EDB3E9E0 /* NavigationRootCoordinator.swift in Sources */,
Expand Down Expand Up @@ -3198,7 +3205,12 @@
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */,
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */,
DF504B10A4918F971A57BEF2 /* PostHogAnalyticsClient.swift in Sources */,
<<<<<<< HEAD
2835FD52F3F618D07F799B3D /* Publisher.swift in Sources */,
||||||| parent of 829ba5a (Fixes #464 - Incorrect regex usage on extended grapheme clusters)
=======
F587A9AF25A262DE5A7B0369 /* ProgressTracker.swift in Sources */,
>>>>>>> 829ba5a (Fixes #464 - Incorrect regex usage on extended grapheme clusters)
743790BF6A5B0577EA74AF14 /* ReadMarkerRoomTimelineItem.swift in Sources */,
8EF63DDDC1B54F122070B04D /* ReadMarkerRoomTimelineView.swift in Sources */,
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */,
Expand Down
40 changes: 40 additions & 0 deletions ElementX/Sources/Other/Extensions/NSRegularExpresion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

/// NSRegularExpressions work internally on NSStrings, we need to be careful how we build the ranges for extended grapheme clusters https://stackoverflow.com/a/27880748/730924
extension NSRegularExpression {
func enumerateMatches(in string: String, options: NSRegularExpression.MatchingOptions = [], using block: (NSTextCheckingResult?, NSRegularExpression.MatchingFlags, UnsafeMutablePointer<ObjCBool>) -> Void) {
enumerateMatches(in: string, options: options, range: .init(location: 0, length: (string as NSString).length), using: block)
}

func matches(in string: String, options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
matches(in: string, options: options, range: .init(location: 0, length: (string as NSString).length))
}

func numberOfMatches(in string: String, options: NSRegularExpression.MatchingOptions = []) -> Int {
numberOfMatches(in: string, options: options, range: .init(location: 0, length: (string as NSString).length))
}

func firstMatch(in string: String, options: NSRegularExpression.MatchingOptions = []) -> NSTextCheckingResult? {
firstMatch(in: string, options: options, range: .init(location: 0, length: (string as NSString).length))
}

func rangeOfFirstMatch(in string: String, options: NSRegularExpression.MatchingOptions = []) -> NSRange {
rangeOfFirstMatch(in: string, options: options, range: .init(location: 0, length: (string as NSString).length))
}
}
11 changes: 5 additions & 6 deletions ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,13 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {

private func addLinks(_ attributedString: NSMutableAttributedString) {
let string = attributedString.string
let range = NSRange(location: 0, length: attributedString.string.count)

var matches = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: [], range: range)
matches.append(contentsOf: MatrixEntityRegex.roomIdentifierRegex.matches(in: string, options: [], range: range))
var matches = MatrixEntityRegex.userIdentifierRegex.matches(in: string, options: [])
matches.append(contentsOf: MatrixEntityRegex.roomIdentifierRegex.matches(in: string, options: []))
// As of right now we do not handle event id links in any way so there is no need to add them as links
// matches.append(contentsOf: MatrixEntityRegex.eventIdentifierRegex.matches(in: string, options: [], range: range))
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: [], range: range))
matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: [], range: range))
// matches.append(contentsOf: MatrixEntityRegex.eventIdentifierRegex.matches(in: string, options: []))
matches.append(contentsOf: MatrixEntityRegex.roomAliasRegex.matches(in: string, options: []))
matches.append(contentsOf: MatrixEntityRegex.linkRegex.matches(in: string, options: []))

guard matches.count > 0 else {
return
Expand Down
10 changes: 5 additions & 5 deletions ElementX/Sources/Other/MatrixEntityRegex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,39 +49,39 @@ enum MatrixEntityRegex: String {
// swiftlint:enable force_try

static func isMatrixHomeserver(_ homeserver: String) -> Bool {
guard let match = homeserverRegex.firstMatch(in: homeserver, range: .init(location: 0, length: homeserver.count)) else {
guard let match = homeserverRegex.firstMatch(in: homeserver) else {
return false
}

return match.range.length == homeserver.count
}

static func isMatrixUserIdentifier(_ identifier: String) -> Bool {
guard let match = userIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
guard let match = userIdentifierRegex.firstMatch(in: identifier) else {
return false
}

return match.range.length == identifier.count
}

static func isMatrixRoomAlias(_ alias: String) -> Bool {
guard let match = roomAliasRegex.firstMatch(in: alias, range: .init(location: 0, length: alias.count)) else {
guard let match = roomAliasRegex.firstMatch(in: alias) else {
return false
}

return match.range.length == alias.count
}

static func isMatrixRoomIdentifier(_ identifier: String) -> Bool {
guard let match = roomIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
guard let match = roomIdentifierRegex.firstMatch(in: identifier) else {
return false
}

return match.range.length == identifier.count
}

static func isMatrixEventIdentifier(_ identifier: String) -> Bool {
guard let match = eventIdentifierRegex.firstMatch(in: identifier, range: .init(location: 0, length: identifier.count)) else {
guard let match = eventIdentifierRegex.firstMatch(in: identifier) else {
return false
}

Expand Down
8 changes: 4 additions & 4 deletions ElementX/Sources/Other/PermalinkBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,18 @@ enum PermalinkBuilder {
fragment = String(fragment.dropFirst(1))
}

if let userIdentifierRange = MatrixEntityRegex.userIdentifierRegex.firstMatch(in: fragment, range: .init(location: 0, length: fragment.count))?.range {
if let userIdentifierRange = MatrixEntityRegex.userIdentifierRegex.firstMatch(in: fragment)?.range {
return .userIdentifier((fragment as NSString).substring(with: userIdentifierRange))
}

if let roomAliasRange = MatrixEntityRegex.roomAliasRegex.firstMatch(in: fragment, range: .init(location: 0, length: fragment.count))?.range {
if let roomAliasRange = MatrixEntityRegex.roomAliasRegex.firstMatch(in: fragment)?.range {
return .roomAlias((fragment as NSString).substring(with: roomAliasRange))
}

if let roomIdentifierRange = MatrixEntityRegex.roomIdentifierRegex.firstMatch(in: fragment, range: .init(location: 0, length: fragment.count))?.range {
if let roomIdentifierRange = MatrixEntityRegex.roomIdentifierRegex.firstMatch(in: fragment)?.range {
let roomIdentifier = (fragment as NSString).substring(with: roomIdentifierRange)

if let eventIdentifierRange = MatrixEntityRegex.eventIdentifierRegex.firstMatch(in: fragment, range: .init(location: 0, length: fragment.count))?.range {
if let eventIdentifierRange = MatrixEntityRegex.eventIdentifierRegex.firstMatch(in: fragment)?.range {
let eventIdentifier = (fragment as NSString).substring(with: eventIdentifierRange)
return .event(roomIdentifier: roomIdentifier, eventIdentifier: eventIdentifier)
}
Expand Down
2 changes: 1 addition & 1 deletion IntegrationTests/Sources/TestMeasurementParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class TestMeasurementParser {
}

let string = String(data: handle.availableData, encoding: .utf8) ?? "<Non-ascii data of size\(handle.availableData.count)>\n"
self.regex.matches(in: string, options: .reportCompletion, range: NSRange(location: 0, length: string.count)).forEach {
self.regex.matches(in: string, options: .reportCompletion).forEach {
if let nameIndex = Range($0.range(at: 1), in: string),
let averageIndex = Range($0.range(at: 3), in: string) {
let name = String(string[nameIndex])
Expand Down
1 change: 1 addition & 0 deletions changelog.d/464.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed incorrect link detection on messages containing emojis

0 comments on commit bb67b92

Please sign in to comment.