Address Indic script crash.

This commit is contained in:
Matthew Chen 2018-02-15 20:45:28 -05:00
parent c60422e921
commit abfd333a17
26 changed files with 428 additions and 95 deletions

View File

@ -38,7 +38,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.19.5</string>
<string>2.19.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@ -55,7 +55,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2.19.5.0</string>
<string>2.19.7.3</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LOGS_EMAIL</key>

View File

@ -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<String>?,
maxFileSize: UInt) -> SignalAttachment {
assert(dataUTI.characters.count > 0)
assert(dataUTI.count > 0)
assert(dataSource != nil)
guard let dataSource = dataSource else {

View File

@ -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 <SignalServiceKit/NSData+hexString.h>
#import <SignalServiceKit/NSDate+OWS.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/OWSFileSystem.h>
#import <SignalServiceKit/OWSMessageSender.h>
#import <SignalServiceKit/OWSRequestBuilder.h>
@ -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;

View File

@ -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 <SignalServiceKit/NSData+Image.h>
#import <SignalServiceKit/NSDate+OWS.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/NSTimer+OWS.h>
#import <SignalServiceKit/OWSAcknowledgeMessageDeliveryRequest.h>
#import <SignalServiceKit/OWSAnalytics.h>

View File

@ -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 = @"";

View File

@ -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<NSString *> *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<NSString *> *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<NSString *> *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<NSString *> *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

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
import Foundation

View File

@ -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 <SignalServiceKit/ContactsUpdater.h>
#import <SignalServiceKit/NSNotificationCenter+OWS.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/OWSError.h>
#import <SignalServiceKit/SignalAccount.h>
#import <SignalServiceKit/TSStorageManager.h>
@ -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 {

View File

@ -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 <AudioToolbox/AudioServices.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/TSCall.h>
#import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSErrorMessage.h>
@ -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.

View File

@ -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 <SignalServiceKit/NSDate+OWS.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/OWSDevice.h>
#import <SignalServiceKit/OWSMessageReceiver.h>
#import <SignalServiceKit/OWSMessageSender.h>
@ -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) {

View File

@ -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
}
}

View File

@ -1,13 +1,13 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import <SignalServiceKit/NSString+SSK.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSString (OWS)
- (NSString *)ows_stripped;
- (NSString *)rtlSafeAppend:(NSString *)string referenceView:(UIView *)referenceView;
@end

View File

@ -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);

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.
//

View File

@ -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<NSString *> *groupMemberIds;
@property (nonatomic, strong) NSString *groupName;
@property (nonatomic, strong) NSData *groupId;
@property (nonatomic) NSArray<NSString *> *groupMemberIds;
@property (nonatomic) NSString *groupName;
@property (nonatomic) NSData *groupId;
#if TARGET_OS_IOS
@property (nonatomic, strong) UIImage *groupImage;

View File

@ -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

View File

@ -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];
}

View File

@ -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

View File

@ -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<UnicodeCodeRange *> *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