diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index c777eb8a1..72c2f04e1 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -38,7 +38,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.19.5 + 2.19.7 CFBundleSignature ???? CFBundleURLTypes @@ -55,7 +55,7 @@ CFBundleVersion - 2.19.5.0 + 2.19.7.3 ITSAppUsesNonExemptEncryption LOGS_EMAIL diff --git a/Signal/src/Models/SignalAttachment.swift b/Signal/src/Models/SignalAttachment.swift index d1e450ac3..5437ef4bc 100644 --- a/Signal/src/Models/SignalAttachment.swift +++ b/Signal/src/Models/SignalAttachment.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import Foundation @@ -91,7 +91,7 @@ class SignalAttachment: NSObject { return dataSource.dataUrl() } public var sourceFilename: String? { - return dataSource.sourceFilename + return dataSource.sourceFilename?.filterStringForDisplay() } public var isValidImage: Bool { return dataSource.isValidImage() @@ -223,7 +223,7 @@ class SignalAttachment: NSObject { if let filename = sourceFilename { let fileExtension = (filename as NSString).pathExtension - if fileExtension.characters.count > 0 { + if fileExtension.count > 0 { if let mimeType = MIMETypeUtil.mimeType(forFileExtension:fileExtension) { // UTI types are an imperfect means of representing file type; // file extensions are also imperfect but far more reliable and @@ -249,7 +249,7 @@ class SignalAttachment: NSObject { // like: "signal-2017-04-24-095918.zip" var filenameOrDefault: String { if let filename = sourceFilename { - return filename + return filename.filterStringForDisplay() } else { let kDefaultAttachmentName = "signal" @@ -271,8 +271,8 @@ class SignalAttachment: NSObject { var fileExtension: String? { if let filename = sourceFilename { let fileExtension = (filename as NSString).pathExtension - if fileExtension.characters.count > 0 { - return fileExtension + if fileExtension.count > 0 { + return fileExtension.filterStringForDisplay() } } if dataUTI == kOversizeTextAttachmentUTI { @@ -475,7 +475,7 @@ class SignalAttachment: NSObject { // NOTE: The attachment returned by this method may not be valid. // Check the attachment's error property. private class func imageAttachment(dataSource: DataSource?, dataUTI: String) -> SignalAttachment { - assert(dataUTI.characters.count > 0) + assert(dataUTI.count > 0) assert(dataSource != nil) guard let dataSource = dataSource else { @@ -575,7 +575,7 @@ class SignalAttachment: NSObject { // NOTE: The attachment returned by this method may nil or not be valid. // Check the attachment's error property. public class func imageAttachment(image: UIImage?, dataUTI: String, filename: String?) -> SignalAttachment { - assert(dataUTI.characters.count > 0) + assert(dataUTI.count > 0) guard let image = image else { let dataSource = DataSourceValue.emptyDataSource() @@ -777,7 +777,7 @@ class SignalAttachment: NSObject { dataUTI: String, validUTISet: Set?, maxFileSize: UInt) -> SignalAttachment { - assert(dataUTI.characters.count > 0) + assert(dataUTI.count > 0) assert(dataSource != nil) guard let dataSource = dataSource else { diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index cc0737759..9ad94c33d 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSProfileManager.h" @@ -11,6 +11,7 @@ #import #import #import +#import #import #import #import @@ -70,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN { @synchronized(self) { - return _profileName; + return _profileName.filterStringForDisplay; } } @@ -78,7 +79,7 @@ NS_ASSUME_NONNULL_BEGIN { @synchronized(self) { - _profileName = [profileName ows_stripped]; + _profileName = profileName.filterStringForDisplay; } } @@ -364,6 +365,8 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; OWSAssert(successBlockParameter); OWSAssert(failureBlockParameter); + profileName = profileName.filterStringForDisplay; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized(self) { @@ -1327,7 +1330,7 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; NSData *unpaddedData = [decryptedData subdataWithRange:NSMakeRange(0, unpaddedLength)]; - return [[NSString alloc] initWithData:unpaddedData encoding:NSUTF8StringEncoding]; + return [[NSString alloc] initWithData:unpaddedData encoding:NSUTF8StringEncoding].filterStringForDisplay; } - (nullable NSData *)encryptProfileData:(nullable NSData *)data @@ -1345,7 +1348,7 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; - (nullable NSData *)encryptProfileNameWithUnpaddedName:(NSString *)name { - NSData *nameData = [name dataUsingEncoding:NSUTF8StringEncoding]; + NSData *nameData = [name.filterStringForDisplay dataUsingEncoding:NSUTF8StringEncoding]; if (nameData.length > kOWSProfileManager_NameDataLength) { OWSFail(@"%@ name data is too long with length:%lu", self.logTag, (unsigned long)nameData.length); return nil; diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index e6808cc49..3f4ce40ff 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -10,8 +10,8 @@ #import "DebugUIPage.h" #import "Environment.h" #import "FingerprintViewController.h" -#import "MediaDetailViewController.h" #import "HomeViewController.h" +#import "MediaDetailViewController.h" #import "NSString+OWS.h" #import "NotificationsManager.h" #import "OWSAnyTouchGestureRecognizer.h" @@ -62,6 +62,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index d920a1ed3..d5a48bfac 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "ConversationViewItem.h" @@ -309,7 +309,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) // Only show up to N characters of text. const NSUInteger kMaxTextDisplayLength = 1024; - NSString *_Nullable fullText = [DisplayableText displayableText:text]; + NSString *_Nullable fullText = text.filterStringForDisplay; BOOL isTextTruncated = NO; if (!fullText) { fullText = @""; diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m index 499878b50..a4d2d5926 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMessages.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "DebugUIMessages.h" @@ -249,6 +249,14 @@ NS_ASSUME_NONNULL_BEGIN actionBlock:^{ [DebugUIMessages injectFakeIncomingMessages:1000 thread:thread]; }], + [OWSTableItem itemWithTitle:@"Test Indic Scripts" + actionBlock:^{ + [DebugUIMessages testIndicScriptsInThread:thread]; + }], + [OWSTableItem itemWithTitle:@"Test Zalgo" + actionBlock:^{ + [DebugUIMessages testZalgoTextInThread:thread]; + }], ] mutableCopy]; if ([thread isKindOfClass:[TSContactThread class]]) { TSContactThread *contactThread = (TSContactThread *)thread; @@ -1431,6 +1439,90 @@ NS_ASSUME_NONNULL_BEGIN }); } ++ (void)testIndicScriptsInThread:(TSThread *)thread +{ + NSArray *strings = @[ + @"\u0C1C\u0C4D\u0C1E\u200C\u0C3E", + @"\u09B8\u09CD\u09B0\u200C\u09C1", + @"non-crashing string", + ]; + + [TSStorageManager.sharedManager.dbReadWriteConnection + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + for (NSString *string in strings) { + // DO NOT log these strings with the debugger attached. + // DDLogInfo(@"%@ %@", self.logTag, string); + + { + TSIncomingMessage *message = + [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + authorId:@"+19174054215" + sourceDeviceId:0 + messageBody:string]; + [message saveWithTransaction:transaction]; + [message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO]; + } + { + NSString *recipientId = @"+19174054215"; + NSString *groupName = string; + NSMutableArray *recipientIds = [@[ + recipientId, + [TSAccountManager localNumber], + ] mutableCopy]; + NSData *groupId = [SecurityUtils generateRandomBytes:16]; + TSGroupModel *groupModel = + [[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId]; + + TSGroupThread *groupThread = + [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction]; + OWSAssert(groupThread); + } + } + }]; +} + ++ (void)testZalgoTextInThread:(TSThread *)thread +{ + NSArray *strings = @[ + @"Ṱ̴̤̺̣͚͚̭̰̤̮̑̓̀͂͘͡h̵̢̤͔̼̗̦̖̬͌̀͒̀͘i̴̮̤͎͎̝̖̻͓̅̆͆̓̎͘͡ͅŝ̡̡̳͔̓͗̾̀̇͒͘͢͢͡͡ ỉ̛̲̩̫̝͉̀̒͐͋̾͘͢͡͞s̶̨̫̞̜̹͛́̇͑̅̒̊̈ s̵͍̲̗̠̗͈̦̬̉̿͂̏̐͆̾͐͊̾ǫ̶͍̼̝̉͊̉͢͜͞͝ͅͅṁ̵̡̨̬̤̝͔̣̄̍̋͊̿̄͋̈ͅe̪̪̻̱͖͚͈̲̍̃͘͠͝ z̷̢̢̛̩̦̱̺̼͑́̉̾ą͕͎̠̮̹̱̓̔̓̈̈́̅̐͢l̵̨͚̜͉̟̜͉͎̃͆͆͒͑̍̈̚͜͞ğ͔̖̫̞͎͍̒̂́̒̿̽̆͟o̶̢̬͚̘̤̪͇̻̒̋̇̊̏͢͡͡͠ͅ t̡̛̥̦̪̮̅̓̑̈́̉̓̽͛͢͡ȩ̡̩͓͈̩͎͗̔͑̌̓͊͆͝x̫̦͓̤͓̘̝̪͊̆͌͊̽̃̏͒͘͘͢ẗ̶̢̨̛̰̯͕͔́̐͗͌͟͠.̷̩̼̼̩̞̘̪́͗̅͊̎̾̅̏̀̕͟ͅ", + @"This is some normal text", + ]; + + [TSStorageManager.sharedManager.dbReadWriteConnection + readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + for (NSString *string in strings) { + DDLogInfo(@"%@ sending zalgo", self.logTag); + + { + TSIncomingMessage *message = + [[TSIncomingMessage alloc] initWithTimestamp:[NSDate ows_millisecondTimeStamp] + inThread:thread + authorId:@"+19174054215" + sourceDeviceId:0 + messageBody:string]; + [message saveWithTransaction:transaction]; + [message markAsReadWithTransaction:transaction sendReadReceipt:NO updateExpiration:NO]; + } + { + NSString *recipientId = @"+19174054215"; + NSString *groupName = string; + NSMutableArray *recipientIds = [@[ + recipientId, + [TSAccountManager localNumber], + ] mutableCopy]; + NSData *groupId = [SecurityUtils generateRandomBytes:16]; + TSGroupModel *groupModel = + [[TSGroupModel alloc] initWithTitle:groupName memberIds:recipientIds image:nil groupId:groupId]; + + TSGroupThread *groupThread = + [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction]; + OWSAssert(groupThread); + } + } + }]; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m index 8b7c8b465..99e864669 100644 --- a/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m +++ b/Signal/src/ViewControllers/DebugUI/DebugUIMisc.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "DebugUIMisc.h" diff --git a/Signal/src/ViewControllers/InboxTableViewCell.m b/Signal/src/ViewControllers/InboxTableViewCell.m index 1419a5147..bd9358546 100644 --- a/Signal/src/ViewControllers/InboxTableViewCell.m +++ b/Signal/src/ViewControllers/InboxTableViewCell.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "InboxTableViewCell.h" @@ -188,7 +188,7 @@ const NSUInteger kAvatarViewDiameter = 52; : [UIColor lightGrayColor]), }]]; } - NSString *displayableText = [DisplayableText displayableText:thread.lastMessageLabel]; + NSString *displayableText = thread.lastMessageLabel.filterStringForDisplay; if (displayableText) { [snippetText appendAttributedString:[[NSAttributedString alloc] initWithString:displayableText diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index b6eb06037..486b01949 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -312,7 +312,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { return nil } let messageBody = displayableText.fullText - guard messageBody.characters.count > 0 else { + guard messageBody.count > 0 else { return nil } return messageBody @@ -500,7 +500,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { nameLabel.autoPinEdge(toSuperviewEdge: .top) valueLabel.autoPinEdge(toSuperviewEdge: .top) - if subtitle.characters.count > 0 { + if subtitle.count > 0 { let subtitleLabel = self.valueLabel(text: subtitle) subtitleLabel.textColor = UIColor.ows_darkGray() row.addSubview(subtitleLabel) @@ -508,7 +508,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { subtitleLabel.autoPinLeading(toTrailingOf: nameLabel, margin: 10) subtitleLabel.autoPinEdge(.top, to: .bottom, of: valueLabel, withOffset: 1) subtitleLabel.autoPinEdge(toSuperviewEdge: .bottom) - } else if value.characters.count > 0 { + } else if value.count > 0 { valueLabel.autoPinEdge(toSuperviewEdge: .bottom) } else { nameLabel.autoPinEdge(toSuperviewEdge: .bottom) diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index c50d966da..dd97da0c5 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import Foundation diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index 90c351a68..b79fc29d9 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSContactsManager.h" @@ -10,6 +10,7 @@ #import "ViewControllerUtils.h" #import #import +#import #import #import #import @@ -372,7 +373,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification OWSAssert(recipientId.length > 0); SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; - return signalAccount.contact.firstName; + return signalAccount.contact.firstName.filterStringForDisplay; } - (NSString *_Nullable)cachedLastNameForRecipientId:(NSString *)recipientId @@ -380,7 +381,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification OWSAssert(recipientId.length > 0); SignalAccount *_Nullable signalAccount = [self signalAccountForRecipientId:recipientId]; - return signalAccount.contact.lastName; + return signalAccount.contact.lastName.filterStringForDisplay; } #pragma mark - View Helpers @@ -420,7 +421,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification indexText]; } - return phoneNumberLabel; + return phoneNumberLabel.filterStringForDisplay; } - (BOOL)phoneNumber:(PhoneNumber *)phoneNumber1 matchesNumber:(PhoneNumber *)phoneNumber2 { diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index 4025d1187..5e827edbc 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "NotificationsManager.h" @@ -10,6 +10,7 @@ #import "PushManager.h" #import "Signal-Swift.h" #import +#import #import #import #import @@ -393,6 +394,8 @@ NSString *const kNotificationsManagerNewMesssageSoundName = @"NewMessage.aifc"; - (void)presentNotification:(UILocalNotification *)notification identifier:(NSString *)identifier { + notification.alertBody = notification.alertBody.filterStringForDisplay; + DispatchMainThreadSafe(^{ // Replace any existing notification // e.g. when an "Incoming Call" notification gets replaced with a "Missed Call" notification. diff --git a/Signal/src/network/PushManager.m b/Signal/src/network/PushManager.m index a4d7a8243..058ede1ec 100644 --- a/Signal/src/network/PushManager.m +++ b/Signal/src/network/PushManager.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "PushManager.h" @@ -9,6 +9,7 @@ #import "Signal-Swift.h" #import "ThreadUtil.h" #import +#import #import #import #import @@ -194,7 +195,8 @@ NSString *const Signal_Message_MarkAsRead_Identifier = @"Signal_Message_MarkAsRe UILocalNotification *failedSendNotif = [[UILocalNotification alloc] init]; failedSendNotif.alertBody = - [NSString stringWithFormat:NSLocalizedString(@"NOTIFICATION_SEND_FAILED", nil), [thread name]]; + [NSString stringWithFormat:NSLocalizedString(@"NOTIFICATION_SEND_FAILED", nil), [thread name]] + .filterStringForDisplay; failedSendNotif.userInfo = @{ Signal_Thread_UserInfo_Key : thread.uniqueId }; [self presentNotification:failedSendNotif checkForCancel:NO]; completionHandler(); @@ -438,6 +440,8 @@ NSString *const PushManagerUserInfoKeysCallBackSignalRecipientId = @"PushManager // TODO: consolidate notification tracking with NotificationsManager, which also maintains a list of notifications. - (void)presentNotification:(UILocalNotification *)notification checkForCancel:(BOOL)checkForCancel { + notification.alertBody = notification.alertBody.filterStringForDisplay; + dispatch_async(dispatch_get_main_queue(), ^{ NSString *threadId = notification.userInfo[Signal_Thread_UserInfo_Key]; if (checkForCancel && threadId != nil) { diff --git a/Signal/src/util/DisplayableText.swift b/Signal/src/util/DisplayableText.swift index 2d12ebd49..014ac8e3d 100644 --- a/Signal/src/util/DisplayableText.swift +++ b/Signal/src/util/DisplayableText.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import Foundation @@ -185,7 +185,7 @@ extension String { if string == "" { return 0 } - if string.characters.count > Int(kMaxJumbomojiCount * kMaxCharactersPerEmojiCount) { + if string.count > Int(kMaxJumbomojiCount * kMaxCharactersPerEmojiCount) { return 0 } guard string.containsOnlyEmoji else { @@ -197,34 +197,4 @@ extension String { } return UInt(emojiCount) } - - // MARK: Filter Methods - - @objc - class func displayableText(_ text: String?) -> String? { - guard let text = text?.ows_stripped() else { - return nil - } - - if (self.hasExcessiveDiacriticals(text: text)) { - Logger.warn("\(TAG) filtering text for excessive diacriticals.") - let filteredText = text.folding(options: .diacriticInsensitive, locale: .current) - return filteredText.ows_stripped() - } - - return text.ows_stripped() - } - - private class func hasExcessiveDiacriticals(text: String) -> Bool { - // discard any zalgo style text, by detecting maximum number of glyphs per character - for char in text.characters.enumerated() { - let scalarCount = String(char.element).unicodeScalars.count - if scalarCount > 4 { - Logger.warn("\(TAG) detected excessive diacriticals at \(char.element) scalarCount: \(scalarCount)") - return true - } - } - - return false - } } diff --git a/Signal/src/util/NSString+OWS.h b/Signal/src/util/NSString+OWS.h index c9b938cbb..ba579ccfc 100644 --- a/Signal/src/util/NSString+OWS.h +++ b/Signal/src/util/NSString+OWS.h @@ -1,13 +1,13 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import + NS_ASSUME_NONNULL_BEGIN @interface NSString (OWS) -- (NSString *)ows_stripped; - - (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView; @end diff --git a/Signal/src/util/NSString+OWS.m b/Signal/src/util/NSString+OWS.m index c14873f98..8fd2fc5bf 100644 --- a/Signal/src/util/NSString+OWS.m +++ b/Signal/src/util/NSString+OWS.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "NSString+OWS.h" @@ -9,11 +9,6 @@ NS_ASSUME_NONNULL_BEGIN @implementation NSString (OWS) -- (NSString *)ows_stripped -{ - return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; -} - - (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView { OWSAssert(string); diff --git a/Signal/test/util/DisplayableTextFilterTest.swift b/Signal/test/util/DisplayableTextFilterTest.swift index 9fbcf481b..e06c4ec42 100644 --- a/Signal/test/util/DisplayableTextFilterTest.swift +++ b/Signal/test/util/DisplayableTextFilterTest.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import XCTest @@ -19,20 +19,20 @@ class DisplayableTextTest: XCTestCase { func testDisplayableText() { // show plain text let boringText = "boring text" - XCTAssertEqual(boringText, DisplayableText.displayableText(boringText)) + XCTAssertEqual(boringText, boringText.filterStringForDisplay()) // show high byte emojis let emojiText = "🇹🇹🌼🇹🇹🌼🇹🇹" - XCTAssertEqual(emojiText, DisplayableText.displayableText(emojiText)) + XCTAssertEqual(emojiText, emojiText.filterStringForDisplay()) // show normal diacritic usage let diacriticalText = "Příliš žluťoučký kůň úpěl ďábelské ódy." - XCTAssertEqual(diacriticalText, DisplayableText.displayableText(diacriticalText)) + XCTAssertEqual(diacriticalText, diacriticalText.filterStringForDisplay()) // filter excessive diacritics - XCTAssertEqual("HAVING TROUBLE READING TEXT?", DisplayableText.displayableText("H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢") ) + XCTAssertEqual("HAVING TROUBLE READING TEXT?", "H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢".filterStringForDisplay() ) - XCTAssertEqual("LGO!", DisplayableText.displayableText("L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓")) + XCTAssertEqual("LGO!", "L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓".filterStringForDisplay()) } func testGlyphCount() { diff --git a/SignalServiceKit/src/Contacts/SignalAccount.m b/SignalServiceKit/src/Contacts/SignalAccount.m index af35a238b..c0cfe9c8d 100644 --- a/SignalServiceKit/src/Contacts/SignalAccount.m +++ b/SignalServiceKit/src/Contacts/SignalAccount.m @@ -1,9 +1,10 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "SignalAccount.h" #import "Contact.h" +#import "NSString+SSK.h" #import "SignalRecipient.h" #import "TSStorageManager.h" @@ -58,7 +59,12 @@ NS_ASSUME_NONNULL_BEGIN ? [NSString stringWithFormat:@"%@ (%@)", baseName, self.multipleAccountLabelText] : baseName); - return displayName; + return displayName.filterStringForDisplay; +} + +- (NSString *)multipleAccountLabelText +{ + return _multipleAccountLabelText.filterStringForDisplay; } @end diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachment.m b/SignalServiceKit/src/Messages/Attachments/TSAttachment.m index be825b882..f7774b960 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachment.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachment.m @@ -1,9 +1,10 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "TSAttachment.h" #import "MIMETypeUtil.h" +#import "NSString+SSK.h" NS_ASSUME_NONNULL_BEGIN @@ -13,6 +14,8 @@ NSUInteger const TSAttachmentSchemaVersion = 4; @property (nonatomic, readonly) NSUInteger attachmentSchemaVersion; +@property (nonatomic, nullable) NSString *sourceFilename; + @end @implementation TSAttachment @@ -184,6 +187,16 @@ NSUInteger const TSAttachmentSchemaVersion = 4; return self.attachmentType == TSAttachmentTypeVoiceMessage; } +- (nullable NSString *)sourceFilename +{ + return _sourceFilename.filterStringForDisplay; +} + +- (NSString *)contentType +{ + return _contentType.filterStringForDisplay; +} + @end NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Messages/Interactions/TSMessage.m b/SignalServiceKit/src/Messages/Interactions/TSMessage.m index 8207da26b..4d0a7d29b 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSMessage.m @@ -1,9 +1,10 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "TSMessage.h" #import "NSDate+OWS.h" +#import "NSString+SSK.h" #import "TSAttachment.h" #import "TSAttachmentPointer.h" #import "TSThread.h" @@ -281,6 +282,11 @@ static const NSUInteger OWSMessageSchemaVersion = 3; return YES; } +- (nullable NSString *)body +{ + return _body.filterStringForDisplay; +} + #pragma mark - Update With... Methods - (void)updateWithExpireStartedAt:(uint64_t)expireStartedAt transaction:(YapDatabaseReadWriteTransaction *)transaction diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index d5d7dedb1..10250ac78 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSMessageManager.h" @@ -7,6 +7,7 @@ #import "Cryptography.h" #import "MimeTypeUtil.h" #import "NSDate+OWS.h" +#import "NSString+SSK.h" #import "NotificationsProtocol.h" #import "OWSAttachmentsProcessor.h" #import "OWSBlockingManager.h" @@ -152,6 +153,13 @@ NS_ASSUME_NONNULL_BEGIN DDLogInfo(@"%@ handling decrypted envelope: %@", self.logTag, [self descriptionForEnvelope:envelope]); + if (!envelope.source.isValidE164) { + DDLogVerbose( + @"%@ incoming envelope has invalid source: %@", self.logTag, [self descriptionForEnvelope:envelope]); + OWSFail(@"%@ incoming envelope has invalid source", self.logTag); + return; + } + OWSAssert(envelope.source.length > 0); OWSAssert(![self isEnvelopeBlocked:envelope]); @@ -880,6 +888,15 @@ NS_ASSUME_NONNULL_BEGIN if (groupId.length > 0) { NSMutableSet *newMemberIds = [NSMutableSet setWithArray:dataMessage.group.members]; + for (NSString *recipientId in newMemberIds) { + if (!recipientId.isValidE164) { + DDLogVerbose(@"%@ incoming group update has invalid group member: %@", + self.logTag, + [self descriptionForEnvelope:envelope]); + OWSFail(@"%@ incoming group update has invalid group member", self.logTag); + return nil; + } + } // Group messages create the group if it doesn't already exist. // diff --git a/SignalServiceKit/src/Messages/TSGroupModel.h b/SignalServiceKit/src/Messages/TSGroupModel.h index 84bf3ef31..80731c1aa 100644 --- a/SignalServiceKit/src/Messages/TSGroupModel.h +++ b/SignalServiceKit/src/Messages/TSGroupModel.h @@ -1,15 +1,15 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // -#import "TSYapDatabaseObject.h" #import "ContactsManagerProtocol.h" +#import "TSYapDatabaseObject.h" @interface TSGroupModel : TSYapDatabaseObject -@property (nonatomic, strong) NSArray *groupMemberIds; -@property (nonatomic, strong) NSString *groupName; -@property (nonatomic, strong) NSData *groupId; +@property (nonatomic) NSArray *groupMemberIds; +@property (nonatomic) NSString *groupName; +@property (nonatomic) NSData *groupId; #if TARGET_OS_IOS @property (nonatomic, strong) UIImage *groupImage; diff --git a/SignalServiceKit/src/Messages/TSGroupModel.m b/SignalServiceKit/src/Messages/TSGroupModel.m index 2844bd3fe..c2c833250 100644 --- a/SignalServiceKit/src/Messages/TSGroupModel.m +++ b/SignalServiceKit/src/Messages/TSGroupModel.m @@ -1,9 +1,10 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "TSGroupModel.h" #import "FunctionalUtil.h" +#import "NSString+SSK.h" @implementation TSGroupModel @@ -102,7 +103,11 @@ return updatedGroupInfoString; } - #endif +- (NSString *)groupName +{ + return _groupName.filterStringForDisplay; +} + @end diff --git a/SignalServiceKit/src/Util/DataSource.m b/SignalServiceKit/src/Util/DataSource.m index 74b353b03..9d11c665f 100755 --- a/SignalServiceKit/src/Util/DataSource.m +++ b/SignalServiceKit/src/Util/DataSource.m @@ -1,10 +1,11 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "DataSource.h" #import "MIMETypeUtil.h" #import "NSData+Image.h" +#import "NSString+SSK.h" NS_ASSUME_NONNULL_BEGIN @@ -139,7 +140,7 @@ NS_ASSUME_NONNULL_BEGIN return nil; } - NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding]; + NSData *data = [text.filterStringForDisplay dataUsingEncoding:NSUTF8StringEncoding]; return [self dataSourceWithData:data fileExtension:kOversizeTextAttachmentFileExtension]; } diff --git a/SignalServiceKit/src/Util/NSString+SSK.h b/SignalServiceKit/src/Util/NSString+SSK.h new file mode 100644 index 000000000..f48cc8fc7 --- /dev/null +++ b/SignalServiceKit/src/Util/NSString+SSK.h @@ -0,0 +1,17 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (SSK) + +- (NSString *)ows_stripped; + +- (NSString *)filterStringForDisplay; + +- (BOOL)isValidE164; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SignalServiceKit/src/Util/NSString+SSK.m b/SignalServiceKit/src/Util/NSString+SSK.m new file mode 100644 index 000000000..c85467221 --- /dev/null +++ b/SignalServiceKit/src/Util/NSString+SSK.m @@ -0,0 +1,199 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +#import "NSString+SSK.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface UnicodeCodeRange : NSObject + +@property (nonatomic) unichar first; +@property (nonatomic) unichar last; + +@end + +#pragma mark - + +@implementation UnicodeCodeRange + ++ (UnicodeCodeRange *)rangeWithStart:(unichar)first last:(unichar)last +{ + OWSAssert(first <= last); + + UnicodeCodeRange *range = [UnicodeCodeRange new]; + range.first = first; + range.last = last; + return range; +} + +- (NSComparisonResult)compare:(UnicodeCodeRange *)other +{ + + return self.first > other.first; +} + +@end + +#pragma mark - + +@implementation NSString (SSK) + +- (NSString *)ows_stripped +{ + return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; +} + ++ (BOOL)shouldFilterIndic +{ + static BOOL result = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + result = (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 0) && !SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(11, 3)); + }); + return result; +} + ++ (BOOL)isIndicVowel:(unichar)c +{ + static NSArray *ranges; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // From: + // https://unicode.org/charts/PDF/U0C00.pdf + // https://unicode.org/charts/PDF/U0980.pdf + // https://unicode.org/charts/PDF/U0900.pdf + ranges = [@[ + // Telugu: + [UnicodeCodeRange rangeWithStart:0xC05 last:0xC14], + [UnicodeCodeRange rangeWithStart:0xC3E last:0xC4C], + [UnicodeCodeRange rangeWithStart:0xC60 last:0xC63], + // Bengali + [UnicodeCodeRange rangeWithStart:0x985 last:0x994], + [UnicodeCodeRange rangeWithStart:0x9BE last:0x9C8], + [UnicodeCodeRange rangeWithStart:0x9CB last:0x9CC], + [UnicodeCodeRange rangeWithStart:0x9E0 last:0x9E3], + // Devanagari + [UnicodeCodeRange rangeWithStart:0x904 last:0x914], + [UnicodeCodeRange rangeWithStart:0x93A last:0x93B], + [UnicodeCodeRange rangeWithStart:0x93E last:0x94C], + [UnicodeCodeRange rangeWithStart:0x94E last:0x94F], + [UnicodeCodeRange rangeWithStart:0x955 last:0x957], + [UnicodeCodeRange rangeWithStart:0x960 last:0x963], + [UnicodeCodeRange rangeWithStart:0x972 last:0x977], + ] sortedArrayUsingSelector:@selector(compare:)]; + }); + + for (UnicodeCodeRange *range in ranges) { + if (c < range.first) { + // For perf, we can take advantage of the fact that the + // ranges are sorted to exit early if the character lies + // before the current range. + return NO; + } + if (range.first <= c && c <= range.last) { + return YES; + } + } + return NO; +} + ++ (NSCharacterSet *)problematicCharacterSetForIndicScript +{ + static NSCharacterSet *characterSet; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + UniChar chars[] = {0x200C}; + NSString *characterSetString = [[NSString alloc] initWithCharacters:chars + length:sizeof(chars) / sizeof(UniChar)]; + characterSet = [NSCharacterSet characterSetWithCharactersInString:characterSetString]; + }); + + return characterSet; +} + +// See: https://manishearth.github.io/blog/2018/02/15/picking-apart-the-crashing-ios-string/ +- (NSString *)filterForIndicScripts +{ + if (!NSString.shouldFilterIndic) { + return self; + } + + if ([self rangeOfCharacterFromSet:[[self class] problematicCharacterSetForIndicScript]].location == NSNotFound) { + return self; + } + + NSMutableString *filteredForIndic = [NSMutableString new]; + for (NSUInteger index = 0; index < self.length; index++) { + unichar c = [self characterAtIndex:index]; + if (c == 0x200C) { + NSUInteger nextIndex = index + 1; + if (nextIndex < self.length) { + unichar next = [self characterAtIndex:nextIndex]; + if ([NSString isIndicVowel:next]) { + // Discard ZWNJ (zero-width non-joiner) whenever we find a ZWNJ + // followed by an Indic (Telugu, Bengali, Devanagari) vowel + // and replace it with 0xFFFD, the Unicode "replacement character." + [filteredForIndic appendFormat:@"\uFFFD"]; + DDLogError(@"%@ Filtered unsafe Indic script.", self.logTag); + // Then discard the vowel too. + index++; + continue; + } + } + } + [filteredForIndic appendFormat:@"%C", c]; + } + return [filteredForIndic copy]; +} + +- (NSString *)filterStringForDisplay +{ + return self.ows_stripped.filterForIndicScripts.filterForExcessiveDiacriticals; +} + +- (NSString *)filterForExcessiveDiacriticals +{ + if (!self.hasExcessiveDiacriticals) { + return self; + } + return [self stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:[NSLocale currentLocale]]; +} + +- (BOOL)hasExcessiveDiacriticals +{ + // discard any zalgo style text, by detecting maximum number of glyphs per character + NSUInteger index = 0; + while (index < self.length) { + // Walk the grapheme clusters in the string. + NSRange range = [self rangeOfComposedCharacterSequenceAtIndex:index]; + if (range.length > 4) { + // There are too many characters in this grapheme cluster. + return YES; + } else if (range.location != index || range.length < 1) { + // This should never happen. + OWSFail( + @"%@ unexpected composed character sequence: %zd, %@", self.logTag, index, NSStringFromRange(range)); + return YES; + } + index = range.location + range.length; + } + return NO; +} + +- (BOOL)isValidE164 +{ + NSError *error = nil; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"^\\+\\d+$" + options:NSRegularExpressionCaseInsensitive + error:&error]; + if (error || !regex) { + OWSFail(@"%@ could not compile regex: %@", self.logTag, error); + return NO; + } + return [regex rangeOfFirstMatchInString:self options:0 range:NSMakeRange(0, self.length)].location != NSNotFound; +} + +@end + +NS_ASSUME_NONNULL_END