From 3e651fb8df4e8db2b4bba2c334bab8ede62d2532 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 9 Mar 2017 01:36:09 -0500 Subject: [PATCH] filter undisplayable text // FREEBIE --- Signal.xcodeproj/project.pbxproj | 10 +++++ .../TSMessageAdapaters/TSMessageAdapter.m | 18 ++++++++ Signal/src/util/DisplayableTextFilter.swift | 41 +++++++++++++++++++ .../src/view controllers/InboxTableViewCell.m | 13 ++++-- .../test/util/DisplayableTextFilterTest.swift | 27 ++++++++++++ .../translations/en.lproj/Localizable.strings | 5 ++- 6 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 Signal/src/util/DisplayableTextFilter.swift create mode 100644 Signal/test/util/DisplayableTextFilterTest.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d88736208..6f85706c0 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; }; 452ECA4E1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */; }; 4531C9C41DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */; }; + 453201251E71100C00F20761 /* DisplayableTextFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 453201241E71100C00F20761 /* DisplayableTextFilter.swift */; }; 45387B041E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = 45387B031E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.m */; }; 453D28B71D32BA5F00D523F0 /* OWSDisplayedMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B61D32BA5F00D523F0 /* OWSDisplayedMessage.m */; }; 453D28BA1D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = 453D28B91D332DB100D523F0 /* OWSMessagesBubblesSizeCalculator.m */; }; @@ -120,6 +121,8 @@ 45E1F3A31DEF1DF000852CF1 /* NoSignalContactsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 45E1F3A21DEF1DF000852CF1 /* NoSignalContactsView.xib */; }; 45E1F3A51DEF20A100852CF1 /* NoSignalContactsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E1F3A41DEF20A100852CF1 /* NoSignalContactsView.swift */; }; 45E2E9201E153B3D00457AA0 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E2E91F1E153B3D00457AA0 /* Strings.swift */; }; + 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; }; + 45E7A6A91E71CC2E00D44FB5 /* DisplayableTextFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 453201241E71100C00F20761 /* DisplayableTextFilter.swift */; }; 45EB32CF1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 45EB32CE1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m */; }; 45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; 45F170AD1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; }; @@ -653,6 +656,7 @@ 452ECA4C1E087E7200E2F016 /* MessageFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MessageFetcherJob.swift; path = Jobs/MessageFetcherJob.swift; sourceTree = ""; }; 4531C9C21DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "JSQMessagesCollectionViewCell+OWS.h"; sourceTree = ""; }; 4531C9C31DD8E6D800F08304 /* JSQMessagesCollectionViewCell+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "JSQMessagesCollectionViewCell+OWS.m"; sourceTree = ""; }; + 453201241E71100C00F20761 /* DisplayableTextFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilter.swift; sourceTree = ""; }; 45387B021E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWS102MoveLoggingPreferenceToUserDefaults.h; path = Migrations/OWS102MoveLoggingPreferenceToUserDefaults.h; sourceTree = ""; }; 45387B031E36D650005D00B3 /* OWS102MoveLoggingPreferenceToUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWS102MoveLoggingPreferenceToUserDefaults.m; path = Migrations/OWS102MoveLoggingPreferenceToUserDefaults.m; sourceTree = ""; }; 453CC0361D08E1A60040EBA3 /* sn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sn; path = translations/sn.lproj/Localizable.strings; sourceTree = ""; }; @@ -734,6 +738,7 @@ 45E282DF1D08E6CC00ADD4C8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = translations/id.lproj/Localizable.strings; sourceTree = ""; }; 45E2E91E1E13EE3500457AA0 /* OWSCallNotificationsAdaptee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSCallNotificationsAdaptee.h; path = UserInterface/OWSCallNotificationsAdaptee.h; sourceTree = ""; }; 45E2E91F1E153B3D00457AA0 /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Strings.swift; path = UserInterface/Strings.swift; sourceTree = ""; }; + 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = ""; }; 45EB32CD1D7465C900735B2E /* OWSLinkedDevicesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSLinkedDevicesTableViewController.h; sourceTree = ""; }; 45EB32CE1D7465C900735B2E /* OWSLinkedDevicesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSLinkedDevicesTableViewController.m; sourceTree = ""; }; 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = ""; }; @@ -2009,6 +2014,7 @@ 450DF2041E0D74AC003D14BE /* Platform.swift */, 45F170D51E315310003FC1F2 /* Weak.swift */, 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */, + 453201241E71100C00F20761 /* DisplayableTextFilter.swift */, ); path = util; sourceTree = ""; @@ -2402,6 +2408,7 @@ B660F6A21C29868000687D6E /* util */ = { isa = PBXGroup; children = ( + 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */, B660F6A31C29868000687D6E /* ConversionsTest.h */, B660F6A41C29868000687D6E /* ConversionsTest.m */, B660F6A51C29868000687D6E /* Crc32Test.h */, @@ -3132,6 +3139,7 @@ B67ADDC41989FF8700E1A773 /* RPServerRequestsManager.m in Sources */, 348F3A4F1E4A533900750D44 /* CallInterstitialViewController.swift in Sources */, EF764C351DB67CC5000D9A87 /* UIViewController+CameraPermissions.m in Sources */, + 453201251E71100C00F20761 /* DisplayableTextFilter.swift in Sources */, 76EB059418170B33006006FC /* HttpManager.m in Sources */, 45CD81EF1DC030E7004C9430 /* AccountManager.swift in Sources */, 76EB05EC18170B33006006FC /* CallState.m in Sources */, @@ -3422,6 +3430,7 @@ B660F7391C29988E00687D6E /* DH3KKeyAgreementProtocol.m in Sources */, B660F73A1C29988E00687D6E /* EC25KeyAgreementParticipant.m in Sources */, B660F73B1C29988E00687D6E /* EC25KeyAgreementProtocol.m in Sources */, + 45E7A6A91E71CC2E00D44FB5 /* DisplayableTextFilter.swift in Sources */, B660F73C1C29988E00687D6E /* EvpKeyAgreement.m in Sources */, 451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */, B660F73D1C29988E00687D6E /* HashChain.m in Sources */, @@ -3546,6 +3555,7 @@ B660F6D71C29868000687D6E /* Crc32Test.m in Sources */, B660F6C51C29868000687D6E /* HashChainTest.m in Sources */, B660F6D01C29868000687D6E /* DecayingSampleEstimatorTest.m in Sources */, + 45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */, B660F6C91C29868000687D6E /* ZrtpTest.m in Sources */, B660F6D11C29868000687D6E /* EventWindowTest.m in Sources */, B660F6BF1C29868000687D6E /* IpEndPointTest.m in Sources */, diff --git a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m index 3704fd017..2e772bb57 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSMessageAdapter.m @@ -14,6 +14,7 @@ #import "TSIncomingMessage.h" #import "TSInfoMessage.h" #import "TSOutgoingMessage.h" +#import "Signal-Swift.h" #import @@ -162,6 +163,11 @@ NSStringFromClass([attachment class])); } } + } else { // no attachment, plain text message + if ([[DisplayableTextFilter new] shouldPreventDisplayOfText:adapter.messageBody]) { + adapter.messageType = TSInfoMessageAdapter; + adapter.messageBody = NSLocalizedString(@"INFO_MESSAGE_UNABLE_TO_DISPLAY_MESSAGE", @"Generic error text when message contents are undisplayable"); + } } } else if ([interaction isKindOfClass:[TSCall class]]) { TSCall *callRecord = (TSCall *)interaction; @@ -352,4 +358,16 @@ return NO; } +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + @end diff --git a/Signal/src/util/DisplayableTextFilter.swift b/Signal/src/util/DisplayableTextFilter.swift new file mode 100644 index 000000000..eb03424fc --- /dev/null +++ b/Signal/src/util/DisplayableTextFilter.swift @@ -0,0 +1,41 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import Foundation + +@objc class DisplayableTextFilter: NSObject { + + // don't bother filtering on small text, lest we inadvertently catch legitimate usage of rare code point stacking + let allowAnyTextLessThanByteSize: Int + + convenience override init() { + self.init(allowAnyTextLessThanByteSize: 10000) + } + + required init(allowAnyTextLessThanByteSize: Int) { + self.allowAnyTextLessThanByteSize = allowAnyTextLessThanByteSize + } + + @objc(shouldPreventDisplayOfText:) + func shouldPreventDisplay(text: String?) -> Bool { + guard let text = text else { + return false + } + + let byteCount = text.lengthOfBytes(using: .utf8) + + guard byteCount >= allowAnyTextLessThanByteSize else { + return false + } + + let characterCount = text.characters.count + // discard any zalgo style text, which we detect by enforcing avg bytes per character ratio. + if byteCount / characterCount > 10 { + return true + } else { + Logger.warn("filtering undisplayable text bytes: \(byteCount), characterCount: \(characterCount)") + return false + } + } +} diff --git a/Signal/src/view controllers/InboxTableViewCell.m b/Signal/src/view controllers/InboxTableViewCell.m index f4239862c..4843b2493 100644 --- a/Signal/src/view controllers/InboxTableViewCell.m +++ b/Signal/src/view controllers/InboxTableViewCell.m @@ -1,5 +1,6 @@ -// Created by Dylan Bourgeois on 27/10/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "InboxTableViewCell.h" #import "Environment.h" @@ -9,6 +10,7 @@ #import "TSGroupThread.h" #import "TSMessagesManager.h" #import "Util.h" +#import "Signal-Swift.h" #import #import @@ -61,7 +63,12 @@ NS_ASSUME_NONNULL_BEGIN } UIImage *avatar = [OWSAvatarBuilder buildImageForThread:thread contactsManager:contactsManager]; self.threadId = thread.uniqueId; - NSString *snippetLabel = thread.lastMessageLabel; + NSString *snippetLabel; + if ([[DisplayableTextFilter new] shouldPreventDisplayOfText:thread.lastMessageLabel]) { + snippetLabel = NSLocalizedString(@"INFO_MESSAGE_UNABLE_TO_DISPLAY_MESSAGE", @"Generic error text when message contents are undisplayable"); + } else { + snippetLabel = thread.lastMessageLabel; + } NSAttributedString *attributedDate = [self dateAttributedString:thread.lastMessageDate]; NSUInteger unreadCount = [[TSMessagesManager sharedManager] unreadMessagesInThread:thread]; diff --git a/Signal/test/util/DisplayableTextFilterTest.swift b/Signal/test/util/DisplayableTextFilterTest.swift new file mode 100644 index 000000000..dc504a3cb --- /dev/null +++ b/Signal/test/util/DisplayableTextFilterTest.swift @@ -0,0 +1,27 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +import XCTest + +class DisplayableTextFilterTest: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testFiltering() { + // Ignore default byte size limitations to test other filtering behaviors + let filter = DisplayableTextFilter(allowAnyTextLessThanByteSize: 0) + + XCTAssertFalse( filter.shouldPreventDisplay(text: "normal text") ) + XCTAssertFalse( filter.shouldPreventDisplay(text: "🇹🇹🌼🇹🇹🌼🇹🇹") ) + XCTAssertTrue( filter.shouldPreventDisplay(text: "L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓") ) + } +} diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 9ff75db5b..3e98c54b5 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -403,6 +403,9 @@ /* No comment provided by engineer. */ "INCOMING_INCOMPLETE_CALL" = "Incomplete incoming call from"; +/* Generic error text when message contents are undisplayable */ +"INFO_MESSAGE_UNABLE_TO_DISPLAY_MESSAGE" = "Unable to display message."; + /* Text for button at the top of the contact picker */ "INVITE_FRIENDS_CONTACT_TABLE_BUTTON" = "Invite Friends to Signal"; @@ -946,7 +949,7 @@ /* No comment provided by engineer. */ "UPDATE_BUTTON_TITLE" = "Update"; -/* Description of CallKit to upgrading (existing) users. Space is tight, try to keep this as short as the English */ +/* Description of CallKit to upgrading (existing) users */ "UPGRADE_EXPERIENCE_CALLKIT_DESCRIPTION" = "Answering calls from your lock screen is easy with iOS call integration. We anonymize your caller by default, so it's private too."; /* button label shown once when when user upgrades app, in context of call kit */