Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for LIB-1416 & Github #846 #853

Merged
merged 3 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Analytics/Classes/Internal/SEGAnalyticsUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ NS_ASSUME_NONNULL_BEGIN

NSString *GenerateUUIDString(void);

// Validation Utils
BOOL serializableDictionaryTypes(NSDictionary *dict);

// Date Utils
NSString *iso8601FormattedString(NSDate *date);

Expand Down Expand Up @@ -33,4 +36,16 @@ NSString *_Nullable SEGIDFA(void);

NSString *SEGEventNameForScreenTitle(NSString *title);

// Deep copy and check NSCoding conformance
@protocol SEGSerializableDeepCopy <NSObject>
-(id _Nullable) serializableDeepCopy;
@end

@interface NSDictionary(SerializableDeepCopy) <SEGSerializableDeepCopy>
@end

@interface NSArray(SerializableDeepCopy) <SEGSerializableDeepCopy>
@end


NS_ASSUME_NONNULL_END
93 changes: 73 additions & 20 deletions Analytics/Classes/Internal/SEGAnalyticsUtils.m
Original file line number Diff line number Diff line change
Expand Up @@ -160,31 +160,12 @@ static id SEGCoerceJSONObject(id obj)
return [obj description];
}

static void AssertDictionaryTypes(id dict)
{
#ifdef DEBUG
assert([dict isKindOfClass:[NSDictionary class]]);
for (id key in dict) {
assert([key isKindOfClass:[NSString class]]);
id value = dict[key];

assert([value isKindOfClass:[NSString class]] ||
[value isKindOfClass:[NSNumber class]] ||
[value isKindOfClass:[NSNull class]] ||
[value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]] ||
[value isKindOfClass:[NSDate class]] ||
[value isKindOfClass:[NSURL class]]);
}
#endif
}

NSDictionary *SEGCoerceDictionary(NSDictionary *dict)
{
// make sure that a new dictionary exists even if the input is null
dict = dict ?: @{};
// assert that the proper types are in the dictionary
AssertDictionaryTypes(dict);
dict = [dict serializableDeepCopy];
// coerce urls, and dates to the proper format
return SEGCoerceJSONObject(dict);
}
Expand Down Expand Up @@ -214,3 +195,75 @@ static void AssertDictionaryTypes(id dict)
{
return [[NSString alloc] initWithFormat:@"Viewed %@ Screen", title];
}


@implementation NSDictionary(SerializableDeepCopy)

- (NSDictionary *)serializableDeepCopy
{
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:self.count];
NSArray *keys = [self allKeys];
for (id key in keys) {
id aValue = [self objectForKey:key];
id theCopy = nil;

if (![aValue conformsToProtocol:@protocol(NSCoding)]) {
#ifdef DEBUG
NSAssert(FALSE, @"key `%@` doesn't conform to NSCoding and can't be serialized for delivery.", key);
#else
SEGLog(@"key `%@` doesn't conform to NSCoding and can't be serialized for delivery.", key);
// simply leave it out since we can't encode it anyway.
continue;
#endif
}

if ([aValue conformsToProtocol:@protocol(SEGSerializableDeepCopy)]) {
theCopy = [aValue serializableDeepCopy];
} else if ([aValue conformsToProtocol:@protocol(NSCopying)]) {
theCopy = [aValue copy];
} else {
theCopy = aValue;
}

[returnDict setValue:theCopy forKey:key];
}

return [returnDict copy];
}

@end


@implementation NSArray(SerializableDeepCopy)

-(NSArray *)serializableDeepCopy
{
NSMutableArray *returnArray = [[NSMutableArray alloc] initWithCapacity:self.count];

for (id aValue in self) {
id theCopy = nil;

if (![aValue conformsToProtocol:@protocol(NSCoding)]) {
#ifdef DEBUG
NSAssert(FALSE, @"type `%@` doesn't conform to NSCoding and can't be serialized for delivery.", NSStringFromClass([aValue class]));
#else
SEGLog(@"type `%@` doesn't conform to NSCoding and can't be serialized for delivery.", NSStringFromClass([aValue class]));
// simply leave it out since we can't encode it anyway.
continue;
#endif
}

if ([aValue conformsToProtocol:@protocol(SEGSerializableDeepCopy)]) {
theCopy = [aValue serializableDeepCopy];
} else if ([aValue conformsToProtocol:@protocol(NSCopying)]) {
theCopy = [aValue copy];
} else {
theCopy = aValue;
}
[returnArray addObject:theCopy];
}

return [returnArray copy];
}

@end
19 changes: 13 additions & 6 deletions Analytics/Classes/Internal/SEGFileStorage.m
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,19 @@ - (void)setPlist:(id _Nonnull)plist forKey:(NSString *)key
- (NSData *_Nullable)dataFromPlist:(nonnull id)plist
{
NSError *error = nil;
NSData *data = [NSPropertyListSerialization dataWithPropertyList:plist
format:NSPropertyListXMLFormat_v1_0
options:0
error:&error];
if (error) {
SEGLog(@"Unable to serialize data from plist object", error, plist);
NSData *data = nil;
// Temporary just-in-case fix for issue #846; Follow-on PR to move away from plist storage.
@try {
data = [NSPropertyListSerialization dataWithPropertyList:plist
format:NSPropertyListXMLFormat_v1_0
options:0
error:&error];
} @catch (NSException *e) {
SEGLog(@"Unable to serialize data from plist object; Exception: %@, plist: %@", e, plist);
} @finally {
if (error) {
SEGLog(@"Unable to serialize data from plist object; Error: %@, plist: %@", error, plist);
}
}
return data;
}
Expand Down