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