diff --git a/FBSDKCoreKit.podspec b/FBSDKCoreKit.podspec index 037b2c2d6..918037804 100644 --- a/FBSDKCoreKit.podspec +++ b/FBSDKCoreKit.podspec @@ -66,7 +66,8 @@ Pod::Spec.new do |s| 'FBSDKCoreKit/FBSDKCoreKit/FBSDKDeviceViewControllerBase.{h,m}', 'FBSDKCoreKit/FBSDKCoreKit/Internal/Device/**/*', 'FBSDKCoreKit/FBSDKCoreKit/Swift/**/*' - ss.tvos.exclude_files = 'FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/Codeless/*', + ss.tvos.exclude_files = 'FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/AAM/*', + 'FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/Codeless/*', 'FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/FBSDKHybridAppEventsScriptMessageHandler.{h,m}', 'FBSDKCoreKit/FBSDKCoreKit/AppLink/**/*', 'FBSDKCoreKit/FBSDKCoreKit/FBSDKGraphErrorRecoveryProcessor.{h,m}', diff --git a/FBSDKCoreKit/FBSDKCoreKit.xcodeproj/project.pbxproj b/FBSDKCoreKit/FBSDKCoreKit.xcodeproj/project.pbxproj index d131d16c1..dffa89ad0 100644 --- a/FBSDKCoreKit/FBSDKCoreKit.xcodeproj/project.pbxproj +++ b/FBSDKCoreKit/FBSDKCoreKit.xcodeproj/project.pbxproj @@ -1270,6 +1270,13 @@ F92F8F8B22BA275B00494727 /* FBSDKURLSessionTask.h in Headers */ = {isa = PBXBuildFile; fileRef = F92F8F7C22BA274300494727 /* FBSDKURLSessionTask.h */; }; F92F8F8C22BA275C00494727 /* FBSDKURLSessionTask.h in Headers */ = {isa = PBXBuildFile; fileRef = F92F8F7C22BA274300494727 /* FBSDKURLSessionTask.h */; }; F92F8F8D22BA275D00494727 /* FBSDKURLSessionTask.h in Headers */ = {isa = PBXBuildFile; fileRef = F92F8F7C22BA274300494727 /* FBSDKURLSessionTask.h */; }; + F952EA472339403900B20652 /* FBSDKMetadataIndexer.m in Sources */ = {isa = PBXBuildFile; fileRef = F952EA452339403900B20652 /* FBSDKMetadataIndexer.m */; }; + F952EA482339403900B20652 /* FBSDKMetadataIndexer.h in Headers */ = {isa = PBXBuildFile; fileRef = F952EA462339403900B20652 /* FBSDKMetadataIndexer.h */; }; + F952EA492339405D00B20652 /* FBSDKMetadataIndexer.m in Sources */ = {isa = PBXBuildFile; fileRef = F952EA452339403900B20652 /* FBSDKMetadataIndexer.m */; }; + F952EA4A2339405F00B20652 /* FBSDKMetadataIndexer.m in Sources */ = {isa = PBXBuildFile; fileRef = F952EA452339403900B20652 /* FBSDKMetadataIndexer.m */; }; + F952EA4B2339406400B20652 /* FBSDKMetadataIndexer.h in Headers */ = {isa = PBXBuildFile; fileRef = F952EA462339403900B20652 /* FBSDKMetadataIndexer.h */; }; + F952EA4C2339406500B20652 /* FBSDKMetadataIndexer.h in Headers */ = {isa = PBXBuildFile; fileRef = F952EA462339403900B20652 /* FBSDKMetadataIndexer.h */; }; + F952EA4F2339432100B20652 /* FBSDKMetadataIndexerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F952EA4E2339432000B20652 /* FBSDKMetadataIndexerTests.m */; }; F96ADE7A21E6ABB400F6043F /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F96ADE6321E6ABB400F6043F /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; F9FD9A6221659F120068DEAF /* FBSDKGateKeeperManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F9FD9A6121659F120068DEAF /* FBSDKGateKeeperManager.h */; }; F9FD9A6321659F120068DEAF /* FBSDKGateKeeperManager.h in Headers */ = {isa = PBXBuildFile; fileRef = F9FD9A6121659F120068DEAF /* FBSDKGateKeeperManager.h */; }; @@ -1734,6 +1741,9 @@ F9169B832155A03C00FA1789 /* FBSDKUserDataStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBSDKUserDataStore.m; sourceTree = ""; }; F92F8F7C22BA274300494727 /* FBSDKURLSessionTask.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBSDKURLSessionTask.h; sourceTree = ""; }; F92F8F7D22BA274300494727 /* FBSDKURLSessionTask.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBSDKURLSessionTask.m; sourceTree = ""; }; + F952EA452339403900B20652 /* FBSDKMetadataIndexer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKMetadataIndexer.m; sourceTree = ""; }; + F952EA462339403900B20652 /* FBSDKMetadataIndexer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSDKMetadataIndexer.h; sourceTree = ""; }; + F952EA4E2339432000B20652 /* FBSDKMetadataIndexerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKMetadataIndexerTests.m; sourceTree = ""; }; F96ADE6321E6ABB400F6043F /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; F9FD9A6121659F120068DEAF /* FBSDKGateKeeperManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FBSDKGateKeeperManager.h; sourceTree = ""; }; F9FD9A7C21659F320068DEAF /* FBSDKGateKeeperManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBSDKGateKeeperManager.m; sourceTree = ""; }; @@ -2238,6 +2248,7 @@ 9D18A8D91A95403F00A41042 /* AppEvents */ = { isa = PBXGroup; children = ( + F952EA4D2339412C00B20652 /* AAM */, C51121C720A27EF50041DC94 /* Codeless */, 9D18A8DA1A95405A00A41042 /* FBSDKAppEventsStateTests.m */, 9D18A8E81A95495D00A41042 /* FBSDKAppEventsUtilityTests.m */, @@ -2456,6 +2467,7 @@ isa = PBXGroup; children = ( 5D9031C0233AAC5D0001450C /* RestrictiveDataFilter */, + F952EA442339403900B20652 /* AAM */, C5696F1D209BBC35009C931F /* Codeless */, 9D0BC1531A8D23DB00BE8BA4 /* FBSDKAppEvents+Internal.h */, 5F7063FA1AF733F300E42ED7 /* FBSDKAppEventsDeviceInfo.h */, @@ -2559,6 +2571,23 @@ path = Swift; sourceTree = ""; }; + F952EA442339403900B20652 /* AAM */ = { + isa = PBXGroup; + children = ( + F952EA462339403900B20652 /* FBSDKMetadataIndexer.h */, + F952EA452339403900B20652 /* FBSDKMetadataIndexer.m */, + ); + path = AAM; + sourceTree = ""; + }; + F952EA4D2339412C00B20652 /* AAM */ = { + isa = PBXGroup; + children = ( + F952EA4E2339432000B20652 /* FBSDKMetadataIndexerTests.m */, + ); + path = AAM; + sourceTree = ""; + }; FD2A237F22FBF7A700DC928F /* ErrorReport */ = { isa = PBXGroup; children = ( @@ -2678,6 +2707,7 @@ C5696F94209BBC35009C931F /* FBSDKEventBinding.h in Headers */, C5C4B3EE2276B88600CA3706 /* FBSDKBasicUtility.h in Headers */, 81B71D531D19C87400933E93 /* FBSDKGraphErrorRecoveryProcessor.h in Headers */, + F952EA4B2339406400B20652 /* FBSDKMetadataIndexer.h in Headers */, 52963A89215992F400C7B252 /* FBSDKAppLinkReturnToRefererController.h in Headers */, 81B71D551D19C87400933E93 /* FBSDKBase64.h in Headers */, 81B71D571D19C87400933E93 /* FBSDKAppEventsUtility.h in Headers */, @@ -2802,6 +2832,7 @@ C5C4B3ED2276B88600CA3706 /* FBSDKBasicUtility.h in Headers */, 9D3AF4501A9EA4BE00EEF724 /* FBSDKErrorConfiguration.h in Headers */, 9D0BC1511A8D236200BE8BA4 /* FBSDKTimeSpentData.h in Headers */, + F952EA482339403900B20652 /* FBSDKMetadataIndexer.h in Headers */, 891687D21AB33CA200F55364 /* FBSDKIcon.h in Headers */, 52963A92215992F400C7B252 /* FBSDKAppLinkReturnToRefererView.h in Headers */, 52963AA82159A13400C7B252 /* FBSDKMeasurementEvent_Internal.h in Headers */, @@ -3222,6 +3253,7 @@ F487DB75231EBCD2008416A9 /* FBSDKHybridAppEventsScriptMessageHandler.h in Headers */, F487DB76231EBCD2008416A9 /* FBSDKWebViewAppLinkResolver.h in Headers */, 5D9A705D23261D6900BF9783 /* FBSDKLibAnalyzer.h in Headers */, + F952EA4C2339406500B20652 /* FBSDKMetadataIndexer.h in Headers */, F487DB77231EBCD2008416A9 /* FBSDKBridgeAPIProtocolWebV2.h in Headers */, F487DB78231EBCD2008416A9 /* FBSDKInternalUtility.h in Headers */, F487DB79231EBCD2008416A9 /* FBSDKConstants.h in Headers */, @@ -3958,6 +3990,7 @@ 52D4F0BC1D91A18D0030B7FC /* FBSDKDeviceRequestsHelper.m in Sources */, 81B71D421D19C87400933E93 /* FBSDKKeychainStoreViaBundleID.m in Sources */, 81B71D431D19C87400933E93 /* FBSDKServerConfiguration.m in Sources */, + F952EA492339405D00B20652 /* FBSDKMetadataIndexer.m in Sources */, 81B71D441D19C87400933E93 /* FBSDKMaleSilhouetteIcon.m in Sources */, C5C7B74F22D84F64004A5A0C /* FBSDKFeatureManager.m in Sources */, C554DB0B2304D11200A32E8B /* FBSDKErrorReport.m in Sources */, @@ -4058,6 +4091,7 @@ 520223F81D83C8D200CE0AB5 /* FBSDKDeviceRequestsHelper.m in Sources */, 9DE1F3CE1A89D9CD00B54D98 /* FBSDKKeychainStoreViaBundleID.m in Sources */, 89830F301A7805E100226ABB /* FBSDKServerConfiguration.m in Sources */, + F952EA472339403900B20652 /* FBSDKMetadataIndexer.m in Sources */, FD8E438122FBF8F1008B6DD3 /* FBSDKErrorReport.m in Sources */, 899C3D031A8C1ED200EA8658 /* FBSDKMaleSilhouetteIcon.m in Sources */, C5C7B74E22D84F64004A5A0C /* FBSDKFeatureManager.m in Sources */, @@ -4086,6 +4120,7 @@ 89C8B19C1A8D7A27009B07F5 /* FBSDKUtilityTests.m in Sources */, C51121CC20A27EF50041DC94 /* FBSDKSampleEventBinding.m in Sources */, 5DBB0447227FEF700009E0A6 /* FBSDKBasicUtilityTests.m in Sources */, + F952EA4F2339432100B20652 /* FBSDKMetadataIndexerTests.m in Sources */, 5D68D7D822BAEEF60063A3E2 /* FBSDKTimeSpentDataTests.m in Sources */, 9D3AF4661A9ED47900EEF724 /* FBSDKGraphRequestConnectionTests.m in Sources */, C51122A020A4BCEB0041DC94 /* FBSDKApplicationDelegateTests.m in Sources */, @@ -4412,6 +4447,7 @@ F487DB2E231EBCD2008416A9 /* FBSDKURLSession.m in Sources */, F487DB30231EBCD2008416A9 /* _FBSDKTemporaryErrorRecoveryAttempter.m in Sources */, F487DB31231EBCD2008416A9 /* FBSDKMeasurementEvent.m in Sources */, + F952EA4A2339405F00B20652 /* FBSDKMetadataIndexer.m in Sources */, F487DB32231EBCD2008416A9 /* FBSDKBridgeAPIProtocolNativeV1.m in Sources */, 5D9A709D2326EB5E00BF9783 /* FBSDKCrashObserver.m in Sources */, F487DB33231EBCD2008416A9 /* FBSDKWebDialogView.m in Sources */, diff --git a/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/AAM/FBSDKMetadataIndexer.h b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/AAM/FBSDKMetadataIndexer.h new file mode 100644 index 000000000..a332a75b2 --- /dev/null +++ b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/AAM/FBSDKMetadataIndexer.h @@ -0,0 +1,27 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FBSDKMetadataIndexer : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/AAM/FBSDKMetadataIndexer.m b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/AAM/FBSDKMetadataIndexer.m new file mode 100644 index 000000000..3efd3b981 --- /dev/null +++ b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/AAM/FBSDKMetadataIndexer.m @@ -0,0 +1,331 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import "FBSDKMetadataIndexer.h" + +#import +#import +#import + +#import + +#import + +static const int FBSDKMetadataIndexerMaxTextLength = 100; +static const int FBSDKMetadataIndexerMaxIndicatorLength = 100; +static const int FBSDKMetadataIndexerMaxValue = 5; + +static NSString * const FIELD_K = @"k"; +static NSString * const FIELD_V = @"v"; +static NSString * const FIELD_K_DELIMITER = @","; + +FBSDKAppEventUserDataType FBSDKAppEventRule1 = @"r1"; +FBSDKAppEventUserDataType FBSDKAppEventRule2 = @"r2"; + +static NSMutableDictionary *> *FBSDKMetadataIndexerRules; +static NSMutableDictionary *> *FBSDKMetadataIndexerStore; +static dispatch_queue_t serialQueue; + +@implementation FBSDKMetadataIndexer + ++ (void)initialize +{ + serialQueue = dispatch_queue_create("com.facebook.appevents.MetadataIndexer", DISPATCH_QUEUE_SERIAL); +} + ++ (void)load +{ + [self initStore]; + [self loadAndSetup]; +} + ++ (void)initStore +{ + FBSDKMetadataIndexerStore = [[NSMutableDictionary alloc] init]; + NSString *userData = [[NSUserDefaults standardUserDefaults] stringForKey:@"com.facebook.appevents.UserDataStore.userData"]; + if (userData) { + NSMutableDictionary * hashedUserData = (NSMutableDictionary *)[NSJSONSerialization JSONObjectWithData:[userData dataUsingEncoding:NSUTF8StringEncoding] + options:NSJSONReadingMutableContainers + error:nil]; + for (NSString *key in FBSDKMetadataIndexerRules) { + if (hashedUserData[key].length > 0) { + FBSDKMetadataIndexerStore[key] = [NSMutableArray arrayWithArray:[hashedUserData[key] componentsSeparatedByString:FIELD_K_DELIMITER]]; + } + } + } + + for (NSString *key in FBSDKMetadataIndexerRules) { + if (!FBSDKMetadataIndexerStore[key]) { + FBSDKMetadataIndexerStore[key] = [[NSMutableArray alloc] init]; + } + } +} + ++ (void)loadAndSetup +{ + FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] + initWithGraphPath:[NSString stringWithFormat:@"%@?fields=aam_rules", [FBSDKSettings appID]] + HTTPMethod:FBSDKHTTPMethodGET]; + [request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) { + if (error) { + return; + } + + if ([result isKindOfClass:[NSDictionary class]]) { + NSString *json = [(NSDictionary *)result objectForKey:@"aam_rules"]; + if (json) { + [FBSDKMetadataIndexer constructRules:[FBSDKBasicUtility objectForJSONString:json error:nil]]; + BOOL isR1Enabled = (nil != [FBSDKMetadataIndexerRules objectForKey:FBSDKAppEventRule1]); + BOOL isR2Enabled = (nil != [FBSDKMetadataIndexerRules objectForKey:FBSDKAppEventRule2]); + if (!isR1Enabled) { + [FBSDKMetadataIndexerStore removeObjectForKey:FBSDKAppEventRule1]; + [FBSDKUserDataStore setHashData:nil forType:FBSDKAppEventRule1]; + } + if (!isR2Enabled) { + [FBSDKMetadataIndexerStore removeObjectForKey:FBSDKAppEventRule2]; + [FBSDKUserDataStore setHashData:nil forType:FBSDKAppEventRule2]; + } + if (isR1Enabled || isR2Enabled) { + [self setupMetadataIndexing]; + } + } + } + }]; +} + ++ (void)constructRules:(NSDictionary *)rules +{ + if (!FBSDKMetadataIndexerRules) { + FBSDKMetadataIndexerRules = [[NSMutableDictionary alloc] init]; + } + + for (NSString *key in rules) { + NSDictionary *value = [self dictionaryValueOf:rules[key]]; + if (value && value[FIELD_K].length > 0 && value[FIELD_V].length > 0) { + FBSDKMetadataIndexerRules[key] = value; + } + } +} + ++ (void)setupMetadataIndexing +{ + void (^block)(UIView *) = ^(UIView *view) { + // Indexing when the view is removed from window and conforms to UITextInput, and skip UIFieldEditor, which is an internval view of UITextField + if (![view window] && ![NSStringFromClass([view class]) isEqualToString:@"UIFieldEditor"] && [view conformsToProtocol:@protocol(UITextInput)]) { + NSString *text = [FBSDKViewHierarchy getText:view]; + NSString *placeholder = [FBSDKViewHierarchy getHint:view]; + BOOL secureTextEntry = [self checkSecureTextEntry:view]; + NSArray *labels = [self getLabelsOfView:view]; + UIKeyboardType keyboardType = [self getKeyboardType:view]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) { + [self getMetadataWithText:[self normalizedValue:text] + placeholder:[self normalizeField:placeholder] + labels:labels + secureTextEntry:secureTextEntry + inputType:keyboardType]; + }); + } + }; + + [FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow) onClass:[UIView class] withBlock:block named:@"metadataIndexingUIView"]; + + // iOS 12: UITextField implements didMoveToWindow without calling parent implementation + if (@available(iOS 12, *)) { + [FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow) onClass:[UITextField class] withBlock:block named:@"metadataIndexingUITextField"]; + } else { + [FBSDKSwizzler swizzleSelector:@selector(didMoveToWindow) onClass:[UIControl class] withBlock:block named:@"metadataIndexingUIControl"]; + } +} + ++ (NSArray *)getSiblingViewsOfView:(UIView *)view +{ + NSObject *parent = [FBSDKViewHierarchy getParent:view]; + if (parent) { + NSArray *views = [FBSDKViewHierarchy getChildren:parent]; + if (views) { + NSMutableArray *siblings = [NSMutableArray arrayWithArray:views]; + [siblings removeObject:view]; + return [siblings copy]; + } + } + return nil; +} + ++ (NSArray *)getLabelsOfView:(UIView *)view +{ + NSMutableArray *labels = [[NSMutableArray alloc] init]; + + NSString *placeholder = [self normalizeField:[FBSDKViewHierarchy getHint:view]]; + if (placeholder) { + [labels addObject:placeholder]; + } + + NSArray *siblingViews = [self getSiblingViewsOfView:view]; + for (id sibling in siblingViews) { + if ([sibling isKindOfClass:[UILabel class]]) { + NSString *text = [self normalizeField:[FBSDKViewHierarchy getText:sibling]]; + if (text) { + [labels addObject:text]; + } + } + } + return [labels copy]; +} + ++ (BOOL)checkSecureTextEntry:(UIView *)view +{ + if ([view isKindOfClass:[UITextField class]]) { + return ((UITextField *)view).secureTextEntry; + } + if ([view isKindOfClass:[UITextView class]]) { + return ((UITextView *)view).secureTextEntry; + } + + return NO; +} + ++ (UIKeyboardType)getKeyboardType:(UIView *)view +{ + if ([view isKindOfClass:[UITextField class]]) { + return ((UITextField *)view).keyboardType; + } + if ([view isKindOfClass:[UITextView class]]) { + return ((UITextView *)view).keyboardType; + } + + return UIKeyboardTypeDefault; +} + ++ (void)getMetadataWithText:(NSString *)text + placeholder:(NSString *)placeholder + labels:(NSArray *)labels + secureTextEntry:(BOOL)secureTextEntry + inputType:(UIKeyboardType)inputType +{ + if (secureTextEntry || + [placeholder containsString:@"password"] || + text.length == 0 || + text.length > FBSDKMetadataIndexerMaxTextLength || + placeholder.length >= FBSDKMetadataIndexerMaxIndicatorLength) { + return; + } + + for (NSString *key in FBSDKMetadataIndexerRules) { + NSDictionary *rule = FBSDKMetadataIndexerRules[key]; + BOOL isRuleKMatched = [self checkMetadataHint:placeholder matchRuleK:rule[FIELD_K]] + || [self checkMetadataLabels:labels matchRuleK:rule[FIELD_K]]; + BOOL isRuleVMatched = [self checkMetadataText:text matchRuleV:rule[FIELD_V]]; + if (isRuleKMatched && isRuleVMatched) { + [FBSDKMetadataIndexer checkAndAppendData:text forKey:key]; + } + } +} + +#pragma mark - Helper Methods + ++ (void)checkAndAppendData:(NSString *)data + forKey:(NSString *)key +{ + NSString *hashData = [FBSDKUtility SHA256Hash:data]; + dispatch_async(serialQueue, ^{ + if (hashData.length == 0 || [FBSDKMetadataIndexerStore[key] containsObject:hashData]) { + return; + } + + while (FBSDKMetadataIndexerStore[key].count >= FBSDKMetadataIndexerMaxValue) { + [FBSDKMetadataIndexerStore[key] removeObjectAtIndex:0]; + } + [FBSDKMetadataIndexerStore[key] addObject:hashData]; + [FBSDKUserDataStore setHashData:[FBSDKMetadataIndexerStore[key] componentsJoinedByString:@","] + forType:key]; + }); +} + ++ (BOOL)checkMetadataLabels:(NSArray *)labels + matchRuleK:(NSString *)ruleK +{ + for (NSString *label in labels) { + if ([self checkMetadataHint:label matchRuleK:ruleK]) { + return YES; + } + } + return NO; +} + ++ (BOOL)checkMetadataHint:(NSString *)hint + matchRuleK:(NSString *)ruleK +{ + if (hint.length > 0 && ruleK) { + NSArray *items = [ruleK componentsSeparatedByString:@","]; + for (NSString *item in items) { + if ([hint containsString:item]) { + return YES; + } + } + } + return NO; +} + ++ (BOOL)checkMetadataText:(NSString *)text + matchRuleV:(NSString *)ruleV +{ + if (text.length > 0 && ruleV) { + NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:ruleV + options:NSRegularExpressionCaseInsensitive + error:nil]; + NSUInteger matches = [regex numberOfMatchesInString:text options:0 range:NSMakeRange(0, text.length)]; + + NSString *prunedText = [[text componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"+- ()."]] componentsJoinedByString:@""]; + NSUInteger prunedMatches = [regex numberOfMatchesInString:prunedText options:0 range:NSMakeRange(0, prunedText.length)]; + + return matches > 0 || prunedMatches > 0; + } + return NO; +} + ++ (NSString *)normalizeField:(NSString *)field +{ + if (!field) { + return nil; + } + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[_-]|\\s" + options:NSRegularExpressionCaseInsensitive + error:nil]; + return [regex stringByReplacingMatchesInString:field + options:0 + range:NSMakeRange(0, field.length) + withTemplate:@""].lowercaseString; +} + ++ (NSString *)normalizedValue:(NSString *)value +{ + if (!value) { + return nil; + } + return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].lowercaseString; +} + ++ (NSDictionary *)dictionaryValueOf:(id)object +{ + if ([object isKindOfClass:[NSDictionary class]]) { + return (NSDictionary *)object; + } + return nil; +} + +@end diff --git a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AAM/FBSDKMetadataIndexerTests.m b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AAM/FBSDKMetadataIndexerTests.m new file mode 100644 index 000000000..dff046e4b --- /dev/null +++ b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AAM/FBSDKMetadataIndexerTests.m @@ -0,0 +1,283 @@ +// Copyright (c) 2014-present, Facebook, Inc. All rights reserved. +// +// You are hereby granted a non-exclusive, worldwide, royalty-free license to use, +// copy, modify, and distribute this software in source code or binary form for use +// in connection with the web services and APIs provided by Facebook. +// +// As with any software that integrates with the Facebook platform, your use of +// this software is subject to the Facebook Developer Principles and Policies +// [http://developers.facebook.com/policy/]. This copyright notice shall be +// included in all copies or substantial portions of the software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#import + +#import + +#import + +#import "FBSDKMetadataIndexer.h" + +extern FBSDKAppEventUserDataType FBSDKAppEventRule1; +extern FBSDKAppEventUserDataType FBSDKAppEventRule2; + +@interface FBSDKMetadataIndexer () ++ (void)constructRules:(NSDictionary *)rules; + ++ (void)initStore; + ++ (BOOL)checkSecureTextEntry:(UIView *)view; + ++ (UIKeyboardType)getKeyboardType:(UIView *)view; + ++ (void)getMetadataWithText:(NSString *)text + placeholder:(NSString *)placeholder + labels:(NSArray *)labels + secureTextEntry:(BOOL)secureTextEntry + inputType:(UIKeyboardType)inputType; + ++ (void)checkAndAppendData:(NSString *)data forKey:(NSString *)key; +@end + +@interface FBSDKMetadataIndexerTests : XCTestCase { + id _mockMetadataIndexer; + UITextField *_emailField; + UITextView *_emailView; + UITextField *_phoneField; + UITextView *_phoneView; + UITextField *_pwdField; + UITextView *_pwdView; +} +@end + +@implementation FBSDKMetadataIndexerTests + +- (void)setUp +{ + _mockMetadataIndexer = OCMClassMock([FBSDKMetadataIndexer class]); + [FBSDKMetadataIndexer initStore]; + [FBSDKMetadataIndexer constructRules:@{ + @"r1": @{ + @"k": @"email,e-mail,em,electronicmail", + @"v": @"^([A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})$", + }, + @"r2": @{ + @"k": @"phone,mobile,contact", + @"v": @"^([0-9]{5,15})$", + } + }]; + + _emailField = [[UITextField alloc] init]; + _emailField.placeholder = @"Enter your email"; + _emailField.keyboardType = UIKeyboardTypeEmailAddress; + + _emailView = [[UITextView alloc] init]; + _emailView.keyboardType = UIKeyboardTypeEmailAddress; + + _phoneField = [[UITextField alloc] init]; + _phoneField.placeholder = @"Enter your phone"; + _phoneField.keyboardType = UIKeyboardTypePhonePad; + + _pwdField = [[UITextField alloc] init]; + _pwdField.placeholder = @"Enter your password"; + _pwdField.secureTextEntry = YES; + + _pwdView = [[UITextView alloc] init]; + _pwdView.secureTextEntry = YES; +} + +- (void)tearDown +{ + [_mockMetadataIndexer stopMocking]; +} + +// test for geting secure text entry in UITextField +- (void)testCheckSecureTextEntryOfTextField +{ + // without secure text + XCTAssertFalse([FBSDKMetadataIndexer checkSecureTextEntry:_emailField], + @"test for UITextField without secure text"); + + // with secure text + XCTAssertTrue([FBSDKMetadataIndexer checkSecureTextEntry:_pwdField], + @"test for UITextField with secure text"); +} + +// test for geting secure text entry in UITextView +- (void)testCheckSecureTextEntryOfTextView +{ + // without secure text + XCTAssertFalse([FBSDKMetadataIndexer checkSecureTextEntry:_emailView], + @"test for UITextView without secure text"); + + // with secure text + XCTAssertTrue([FBSDKMetadataIndexer checkSecureTextEntry:_pwdView], @"test for UITextView with secure text"); +} + +// test for geting keyboard type from UITextField +- (void)testGetKeyboardTypeOfTextField +{ + XCTAssertEqual(_emailField.keyboardType, + [FBSDKMetadataIndexer getKeyboardType:_emailField], + @"test for geting keyboard type from UITextField"); +} + +// test for geting keyboard type from UITextView +- (void)testGetKeyboardTypeOfTextView +{ + XCTAssertEqual(_emailView.keyboardType, + [FBSDKMetadataIndexer getKeyboardType:_emailView], + @"test for geting keyboard type from UITextView"); +} + +// test for geting metadata with valid email +- (void)testGetMetadataWithEmail +{ + NSString *text = @"test@fb.com"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your email" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypeEmailAddress]; + OCMVerify([_mockMetadataIndexer checkAndAppendData:text forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with valid phone number +- (void)testGetMetadataWithPhoneNumber +{ + NSString *text = @"1112223333"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your phone number" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypePhonePad]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMVerify([_mockMetadataIndexer checkAndAppendData:text forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with secure text +- (void)testGetMetadataWithSecureText +{ + NSString *text = @"dfjald1314"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your password" + labels:nil + secureTextEntry:YES + inputType:UIKeyboardTypeDefault]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with invalid email +- (void)testGetMetadataWithInvalidEmail +{ + NSString *text = @"test"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your email" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypeEmailAddress]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with invalid email placeholder +- (void)testGetMetadataWithInvalidEmailPlaceholder +{ + NSString *text = @"test@fb.com"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypeEmailAddress]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with invalid phone number +- (void)testGetMetadataWithInvalidPhoneNumber +{ + NSString *text = @"1234"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your phone number" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypePhonePad]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with invalid phone number placeholder +- (void)testGetMetadataWithInvalidPhoneNumberPlaceholder +{ + NSString *text = @"1112223333"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypePhonePad]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with text which is neither email nor phone number +- (void)testGetMetadataWithTextNotEmailAndPhone +{ + NSString *text = @"Facebook"; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your name" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypeAlphabet]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with no text +- (void)testGetMetadataWithNoText +{ + NSString *text = @""; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your email" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypeEmailAddress]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with too long text +- (void)testGetMetadataWithTooLongText +{ + NSString *text = [NSString stringWithFormat:@"%@%@", [@"" stringByPaddingToLength:1000 withString: @"a" startingAtIndex:0], @"@fb.com"]; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:@"enter your email" + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypeEmailAddress]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +// test for geting metadata with too long placeholder +- (void)testGetMetadataWithTooLongPlaceholder +{ + NSString *text = @"test@fb.com"; + NSString *indicator = [NSString stringWithFormat:@"%@", [@"" stringByPaddingToLength:1000 withString: @"enter your email " startingAtIndex:0]]; + [FBSDKMetadataIndexer getMetadataWithText:text + placeholder:indicator + labels:nil + secureTextEntry:NO + inputType:UIKeyboardTypeEmailAddress]; + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule1]); + OCMReject([_mockMetadataIndexer checkAndAppendData:[OCMArg any] forKey:FBSDKAppEventRule2]); +} + +@end