From eefb37453ea0a4c84c51e36d787d0c1b08f69781 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Fri, 11 Aug 2017 16:39:05 -0700 Subject: [PATCH 1/6] bump to 9.0, replace CFURLCreateStringByAddingPercentEscapes --- Amplitude.xcodeproj/project.pbxproj | 10 +++++----- .../xcshareddata/xcschemes/Amplitude.xcscheme | 2 +- .../xcshareddata/xcschemes/AmplitudeFramework.xcscheme | 2 +- .../xcshareddata/xcschemes/AmplitudeTVOS.xcscheme | 2 +- .../xcshareddata/xcschemes/AmplitudeTests.xcscheme | 2 +- Amplitude/Amplitude.m | 7 +------ 6 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Amplitude.xcodeproj/project.pbxproj b/Amplitude.xcodeproj/project.pbxproj index 9889a6c3..d02b9106 100644 --- a/Amplitude.xcodeproj/project.pbxproj +++ b/Amplitude.xcodeproj/project.pbxproj @@ -457,7 +457,7 @@ E98C051B1A48E7FE00800C63 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0830; ORGANIZATIONNAME = Amplitude; TargetAttributes = { 343AB4161CC99F4F00962943 = { @@ -724,7 +724,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_OBJC_ARC = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; @@ -753,7 +753,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_OBJC_ARC = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -858,7 +858,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 6.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-lsqlite3.0"; @@ -900,7 +900,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 6.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = "-lsqlite3.0"; SDKROOT = iphoneos; diff --git a/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme b/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme index 18f3245c..2685b0c4 100644 --- a/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme +++ b/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme @@ -1,6 +1,6 @@ %{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); #else - newString = NSMakeCollectable(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, - (CFStringRef)string, - NULL, - CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), - CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))); - SAFE_ARC_AUTORELEASE(newString); + newString = [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; #endif if (newString) { return newString; From 841aa3950c65c559adc863fed3de267d6c569d61 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Mon, 14 Aug 2017 14:41:20 -0700 Subject: [PATCH 2/6] replace with nsurlsessions and update tests --- Amplitude/Amplitude.m | 17 ++++++++-------- AmplitudeTests/AmplitudeTests.m | 32 ++++++++++++++++-------------- AmplitudeTests/AmplitudeiOSTests.m | 20 +++++++++---------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index f6688397..8416ffd0 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -964,13 +964,14 @@ - (void)makeEventUploadPostRequest:(NSString*) url events:(NSString*) events num SAFE_ARC_RELEASE(postData); - // If pinning is enabled, use the AMPURLConnection that handles it. -#if AMPLITUDE_SSL_PINNING - id Connection = (self.sslPinningEnabled ? [AMPURLConnection class] : [NSURLConnection class]); -#else - id Connection = [NSURLConnection class]; -#endif - [Connection sendAsynchronousRequest:request queue:_backgroundQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { +// // If pinning is enabled, use the AMPURLConnection that handles it. +//#if AMPLITUDE_SSL_PINNING +// id Connection = (self.sslPinningEnabled ? [AMPURLConnection class] : [NSURLConnection class]); +//#else +// id Connection = [NSURLConnection class]; +//#endif +// [Connection sendAsynchronousRequest:request queue:_backgroundQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { + [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { BOOL uploadSuccessful = NO; NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; if (response != nil) { @@ -1051,7 +1052,7 @@ - (void)makeEventUploadPostRequest:(NSString*) url events:(NSString*) events num _uploadTaskID = UIBackgroundTaskInvalid; } } - }]; + }] resume]; } #pragma mark - application lifecycle methods diff --git a/AmplitudeTests/AmplitudeTests.m b/AmplitudeTests/AmplitudeTests.m index 4c69f7ae..0caf361d 100644 --- a/AmplitudeTests/AmplitudeTests.m +++ b/AmplitudeTests/AmplitudeTests.m @@ -29,28 +29,28 @@ @interface AmplitudeTests : BaseTestCase @end @implementation AmplitudeTests { - id _connectionMock; + id _sharedSessionMock; int _connectionCallCount; } - (void)setUp { [super setUp]; - _connectionMock = [OCMockObject mockForClass:NSURLConnection.class]; + _sharedSessionMock = [OCMockObject partialMockForObject:[NSURLSession sharedSession]]; _connectionCallCount = 0; [self.amplitude initializeApiKey:apiKey]; } - (void)tearDown { - [_connectionMock stopMocking]; + [_sharedSessionMock stopMocking]; } -- (void)setupAsyncResponse:(id) connectionMock response:(NSMutableDictionary*) serverResponse { - [[[connectionMock expect] andDo:^(NSInvocation *invocation) { +- (void)setupAsyncResponse: (NSMutableDictionary*) serverResponse { + [[[_sharedSessionMock stub] andDo:^(NSInvocation *invocation) { _connectionCallCount++; void (^handler)(NSURLResponse*, NSData*, NSError*); - [invocation getArgument:&handler atIndex:4]; - handler(serverResponse[@"response"], serverResponse[@"data"], serverResponse[@"error"]); - }] sendAsynchronousRequest:OCMOCK_ANY queue:OCMOCK_ANY completionHandler:OCMOCK_ANY]; + [invocation getArgument:&handler atIndex:3]; + handler(serverResponse[@"data"], serverResponse[@"response"], serverResponse[@"error"]); + }] dataTaskWithRequest:OCMOCK_ANY completionHandler:OCMOCK_ANY]; } - (void)testInstanceWithName { @@ -257,7 +257,7 @@ - (void)testRequestTooLargeBackoffLogic { }]; // 413 error force backoff with 2 events --> new upload limit will be 1 - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; [self.amplitude logEvent:@"test"]; [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; @@ -265,7 +265,9 @@ - (void)testRequestTooLargeBackoffLogic { // after first 413, the backoffupload batch size should now be 1 XCTAssertTrue(self.amplitude.backoffUpload); XCTAssertEqual(self.amplitude.backoffUploadBatchSize, 1); - XCTAssertEqual(_connectionCallCount, 1); + + // 3 upload attempts: upload 2 events, upload first event fail -> remove first event, upload second event fail -> remove second event + XCTAssertEqual(_connectionCallCount, 3); } - (void)testRequestTooLargeBackoffRemoveEvent { @@ -276,7 +278,7 @@ - (void)testRequestTooLargeBackoffRemoveEvent { }]; // 413 error force backoff with 1 events --> should drop the event - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; [self.amplitude logEvent:@"test"]; [self.amplitude flushQueue]; @@ -325,7 +327,7 @@ - (void)testIdentify { @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] }]; - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; AMPIdentify *identify2 = [[[AMPIdentify alloc] init] set:@"key2" value:@"value2"]; [self.amplitude identify:identify2]; SAFE_ARC_RELEASE(identify2); @@ -400,7 +402,7 @@ - (void)testMergeEventsAndIdentifys { @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] }]; - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; [self.amplitude logEvent:@"test_event1"]; [self.amplitude identify:[[AMPIdentify identify] add:@"photoCount" value:[NSNumber numberWithInt:1]]]; @@ -559,7 +561,7 @@ -(void)testSetOffline { @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] }]; - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; [self.amplitude setOffline:YES]; [self.amplitude logEvent:@"test"]; @@ -588,7 +590,7 @@ -(void)testSetOfflineTruncate { @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"success" dataUsingEncoding:NSUTF8StringEncoding] }]; - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; [self.amplitude setOffline:YES]; [self.amplitude logEvent:@"test1"]; diff --git a/AmplitudeTests/AmplitudeiOSTests.m b/AmplitudeTests/AmplitudeiOSTests.m index 65b56ceb..f73217e0 100644 --- a/AmplitudeTests/AmplitudeiOSTests.m +++ b/AmplitudeTests/AmplitudeiOSTests.m @@ -29,28 +29,28 @@ @interface AmplitudeiOSTests : BaseTestCase @end @implementation AmplitudeiOSTests { - id _connectionMock; + id _sharedSessionMock; int _connectionCallCount; } - (void)setUp { [super setUp]; - _connectionMock = [OCMockObject mockForClass:NSURLConnection.class]; + _sharedSessionMock = [OCMockObject partialMockForObject:[NSURLSession sharedSession]]; _connectionCallCount = 0; [self.amplitude initializeApiKey:apiKey]; } - (void)tearDown { - [_connectionMock stopMocking]; + [_sharedSessionMock stopMocking]; } -- (void)setupAsyncResponse:(id) connectionMock response:(NSMutableDictionary*) serverResponse { - [[[connectionMock expect] andDo:^(NSInvocation *invocation) { +- (void)setupAsyncResponse: (NSMutableDictionary*) serverResponse { + [[[_sharedSessionMock stub] andDo:^(NSInvocation *invocation) { _connectionCallCount++; void (^handler)(NSURLResponse*, NSData*, NSError*); - [invocation getArgument:&handler atIndex:4]; - handler(serverResponse[@"response"], serverResponse[@"data"], serverResponse[@"error"]); - }] sendAsynchronousRequest:OCMOCK_ANY queue:OCMOCK_ANY completionHandler:OCMOCK_ANY]; + [invocation getArgument:&handler atIndex:3]; + handler(serverResponse[@"data"], serverResponse[@"response"], serverResponse[@"error"]); + }] dataTaskWithRequest:OCMOCK_ANY completionHandler:OCMOCK_ANY]; } - (void)testLogEventUploadLogic { @@ -59,7 +59,7 @@ - (void)testLogEventUploadLogic { @"data" : [@"bad_checksum" dataUsingEncoding:NSUTF8StringEncoding] }]; - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; for (int i = 0; i < kAMPEventUploadThreshold; i++) { [self.amplitude logEvent:@"test"]; } @@ -70,7 +70,7 @@ - (void)testLogEventUploadLogic { XCTAssertEqual([self.amplitude queuedEventCount], kAMPEventUploadThreshold + 1); [serverResponse setValue:[@"request_db_write_failed" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"]; - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; for (int i = 0; i < kAMPEventUploadThreshold; i++) { [self.amplitude logEvent:@"test"]; } From 8dcd3d305425cc7545e4c29a59644b5236496d10 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Tue, 15 Aug 2017 12:37:37 -0700 Subject: [PATCH 3/6] remove arc logic --- Amplitude/Amplitude.m | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index 8416ffd0..71814240 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -1535,21 +1535,7 @@ - (NSString*)md5HexDigest:(NSString*)input - (NSString*)urlEncodeString:(NSString*) string { - NSString *newString; -#if __has_feature(objc_arc) - newString = (__bridge_transfer NSString*) - CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, - (__bridge CFStringRef)string, - NULL, - CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), - CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)); -#else - newString = [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; -#endif - if (newString) { - return newString; - } - return @""; + return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; } - (NSDate*) currentTime From 87835a8112741c931cd6b135b806de504e4cd3c0 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Thu, 14 Sep 2017 20:36:15 -0700 Subject: [PATCH 4/6] update to xcode 9, first attempt at updating ssl pinning --- Amplitude.xcodeproj/project.pbxproj | 28 ++++++- .../xcshareddata/xcschemes/Amplitude.xcscheme | 4 +- .../xcschemes/AmplitudeFramework.xcscheme | 4 +- .../xcschemes/AmplitudeTVOS.xcscheme | 4 +- .../xcschemes/AmplitudeTests.xcscheme | 4 +- Amplitude/AMPURLSession.h | 18 +++++ Amplitude/AMPURLSession.m | 75 +++++++++++++++++++ Amplitude/Amplitude.m | 16 ++-- AmplitudeTests/AmplitudeTVOSTests.m | 18 ++--- AmplitudeTests/SSLPinningTests.m | 17 ++++- 10 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 Amplitude/AMPURLSession.h create mode 100644 Amplitude/AMPURLSession.m diff --git a/Amplitude.xcodeproj/project.pbxproj b/Amplitude.xcodeproj/project.pbxproj index d02b9106..30f3a532 100644 --- a/Amplitude.xcodeproj/project.pbxproj +++ b/Amplitude.xcodeproj/project.pbxproj @@ -63,6 +63,11 @@ 60227C0C1CC5AC2F007C117B /* AMPRevenue.m in Sources */ = {isa = PBXBuildFile; fileRef = 60227C0A1CC5AB8A007C117B /* AMPRevenue.m */; }; 60227C0E1CC5BC07007C117B /* RevenueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 60227C0D1CC5BC07007C117B /* RevenueTests.m */; }; 602A9A6B1B754E7B0067230C /* AmplitudeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 602A9A6A1B754E7B0067230C /* AmplitudeTests.m */; }; + 60AFE7321F6B637100DF9A88 /* AMPURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 60AFE7311F6B636F00DF9A88 /* AMPURLSession.m */; }; + 60AFE7331F6B70EF00DF9A88 /* AMPURLSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 60AFE7301F6B636B00DF9A88 /* AMPURLSession.h */; }; + 60AFE7341F6B70F300DF9A88 /* AMPURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 60AFE7311F6B636F00DF9A88 /* AMPURLSession.m */; }; + 60AFE7351F6B70F300DF9A88 /* AMPURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 60AFE7311F6B636F00DF9A88 /* AMPURLSession.m */; }; + 60AFE7361F6B70F400DF9A88 /* AMPURLSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 60AFE7311F6B636F00DF9A88 /* AMPURLSession.m */; }; 60BA92771C2376680043178E /* AMPDatabaseHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 60BA92721C2376680043178E /* AMPDatabaseHelper.m */; }; 60BA92781C2376680043178E /* AMPIdentify.m in Sources */ = {isa = PBXBuildFile; fileRef = 60BA92741C2376680043178E /* AMPIdentify.m */; }; 60BA92791C2376680043178E /* AMPUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 60BA92761C2376680043178E /* AMPUtils.m */; }; @@ -125,6 +130,8 @@ 60227C0A1CC5AB8A007C117B /* AMPRevenue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMPRevenue.m; sourceTree = ""; }; 60227C0D1CC5BC07007C117B /* RevenueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RevenueTests.m; sourceTree = ""; }; 602A9A6A1B754E7B0067230C /* AmplitudeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AmplitudeTests.m; sourceTree = ""; }; + 60AFE7301F6B636B00DF9A88 /* AMPURLSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMPURLSession.h; sourceTree = ""; }; + 60AFE7311F6B636F00DF9A88 /* AMPURLSession.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMPURLSession.m; sourceTree = ""; }; 60BA92711C2376680043178E /* AMPDatabaseHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMPDatabaseHelper.h; sourceTree = ""; }; 60BA92721C2376680043178E /* AMPDatabaseHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AMPDatabaseHelper.m; sourceTree = ""; }; 60BA92731C2376680043178E /* AMPIdentify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AMPIdentify.h; sourceTree = ""; }; @@ -290,6 +297,8 @@ E98C05251A48E7FE00800C63 /* Amplitude */ = { isa = PBXGroup; children = ( + 60AFE7301F6B636B00DF9A88 /* AMPURLSession.h */, + 60AFE7311F6B636F00DF9A88 /* AMPURLSession.m */, E96785E11A48E93F00887CCD /* AMPARCMacros.h */, E96785E21A48E93F00887CCD /* AMPConstants.h */, 9DC708591AD4B28300949778 /* AMPConstants.m */, @@ -365,6 +374,7 @@ 343AB42E1CC9A1EA00962943 /* AMPConstants.h in Headers */, 343AB4381CC9A1EA00962943 /* ISPCertificatePinning.h in Headers */, 343AB42D1CC9A1EA00962943 /* AMPARCMacros.h in Headers */, + 60AFE7331F6B70EF00DF9A88 /* AMPURLSession.h in Headers */, 343AB43A1CC9A1EA00962943 /* ISPPinnedNSURLSessionDelegate.h in Headers */, 343AB4311CC9A1EA00962943 /* AMPIdentify.h in Headers */, 343AB4361CC9A1EA00962943 /* AMPURLConnection.h in Headers */, @@ -457,7 +467,7 @@ E98C051B1A48E7FE00800C63 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; + LastUpgradeCheck = 0900; ORGANIZATIONNAME = Amplitude; TargetAttributes = { 343AB4161CC99F4F00962943 = { @@ -630,6 +640,7 @@ 343AB4281CC99FD500962943 /* ISPCertificatePinning.m in Sources */, 343AB4221CC99FBD00962943 /* AMPIdentify.m in Sources */, 343AB4241CC99FC200962943 /* AMPLocationManagerDelegate.m in Sources */, + 60AFE7351F6B70F300DF9A88 /* AMPURLSession.m in Sources */, 343AB4271CC99FC900962943 /* AMPUtils.m in Sources */, 343AB4261CC99FC700962943 /* AMPURLConnection.m in Sources */, 343AB4201CC99FB800962943 /* AMPDatabaseHelper.m in Sources */, @@ -646,6 +657,7 @@ 600CBC731E2EF61F001F58A9 /* AMPUtils.m in Sources */, 600CBC7C1E2EF63F001F58A9 /* AmplitudeTests.m in Sources */, 600CBC821E2EF654001F58A9 /* SessionTests.m in Sources */, + 60AFE7361F6B70F400DF9A88 /* AMPURLSession.m in Sources */, 600CBC791E2EF637001F58A9 /* AMPDatabaseHelperTests.m in Sources */, 600CBC761E2EF629001F58A9 /* ISPCertificatePinning.m in Sources */, 600CBC7A1E2EF63A001F58A9 /* Amplitude+Test.m in Sources */, @@ -677,6 +689,7 @@ E96785EC1A48E93F00887CCD /* AMPDeviceInfo.m in Sources */, E96785EE1A48E93F00887CCD /* AMPLocationManagerDelegate.m in Sources */, 60BA92791C2376680043178E /* AMPUtils.m in Sources */, + 60AFE7321F6B637100DF9A88 /* AMPURLSession.m in Sources */, 9DC7085A1AD4B28300949778 /* AMPConstants.m in Sources */, 9D6E19551ACCDE1C00352C6C /* ISPPinnedNSURLSessionDelegate.m in Sources */, E96785ED1A48E93F00887CCD /* Amplitude.m in Sources */, @@ -695,6 +708,7 @@ 602A9A6B1B754E7B0067230C /* AmplitudeTests.m in Sources */, 9DFBB9CC1AB0D47A0017F703 /* SessionTests.m in Sources */, E96786001A48FBD100887CCD /* AMPDeviceInfo.m in Sources */, + 60AFE7341F6B70F300DF9A88 /* AMPURLSession.m in Sources */, 9DFBB9CA1AB0D26E0017F703 /* BaseTestCase.m in Sources */, 60BA927C1C23767D0043178E /* AMPUtils.m in Sources */, 601AF94B1E2EF2A1006CE4BA /* AmplitudeiOSTests.m in Sources */, @@ -827,14 +841,20 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -876,14 +896,20 @@ CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO; CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; diff --git a/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme b/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme index 2685b0c4..a6ca5632 100644 --- a/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme +++ b/Amplitude.xcodeproj/xcshareddata/xcschemes/Amplitude.xcscheme @@ -1,6 +1,6 @@ @@ -36,6 +37,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeFramework.xcscheme b/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeFramework.xcscheme index 5a3bfd18..73841598 100644 --- a/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeFramework.xcscheme +++ b/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeFramework.xcscheme @@ -1,6 +1,6 @@ @@ -36,6 +37,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeTVOS.xcscheme b/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeTVOS.xcscheme index c1bcf1c4..c4d803a9 100644 --- a/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeTVOS.xcscheme +++ b/Amplitude.xcodeproj/xcshareddata/xcschemes/AmplitudeTVOS.xcscheme @@ -1,6 +1,6 @@ +#import "ISPPinnedNSURLSessionDelegate.h" + +@interface AMPURLSession : ISPPinnedNSURLSessionDelegate + ++ (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; + +@end +#endif diff --git a/Amplitude/AMPURLSession.m b/Amplitude/AMPURLSession.m new file mode 100644 index 00000000..fbe035df --- /dev/null +++ b/Amplitude/AMPURLSession.m @@ -0,0 +1,75 @@ +#if AMPLITUDE_SSL_PINNING +// +// AMPURLSession.m +// Amplitude +// +// Created by Daniel Jih on 9/14/17. +// Copyright (c) 2017 Amplitude. All rights reserved. +// + +#import "AMPURLSession.h" +#import "AMPARCMacros.h" +#import "AMPConstants.h" +#import "ISPCertificatePinning.h" +#import "ISPPinnedNSURLSessionDelegate.h" + +@interface AMPURLSession () + +@end + +@implementation AMPURLSession + ++ (void)initialize +{ + if (self == [AMPURLSession class]) { + [AMPURLSession pinSSLCertificate:@[@"ComodoRsaCA", @"ComodoRsaDomainValidationCA"]]; + } +} + ++ (void)pinSSLCertificate:(NSArray *)certFilenames +{ + // We pin the anchor/CA certificates + NSMutableArray *certs = [NSMutableArray array]; + for (NSString *certFilename in certFilenames) { + NSString *certPath = [[NSBundle bundleForClass:[self class]] pathForResource:certFilename ofType:@"der"]; + NSData *certData = SAFE_ARC_AUTORELEASE([[NSData alloc] initWithContentsOfFile:certPath]); + if (certData == nil) { + NSLog(@"Failed to load a certificate"); + return; + } + [certs addObject:certData]; + } + + NSMutableDictionary *pins = [[NSMutableDictionary alloc] init]; + [pins setObject:certs forKey:kAMPEventLogDomain]; + + if (pins == nil) { + NSLog(@"Failed to pin a certificate"); + return; + } + + // Save the SSL pins so that our connection delegates automatically use them + if ([ISPCertificatePinning setupSSLPinsUsingDictionnary:pins] != YES) { + NSLog(@"Failed to pin the certificates"); + SAFE_ARC_RELEASE(pins); + return; + } + SAFE_ARC_RELEASE(pins); +} + +- (void)dealloc +{ + SAFE_ARC_SUPER_DEALLOC(); +} + ++ (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler { + + AMPURLSession *delegate = SAFE_ARC_AUTORELEASE([[AMPURLSession alloc] init]); + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil]; + return [session dataTaskWithRequest:request completionHandler:completionHandler]; +} + +@end +#endif diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index 71814240..21c1390a 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -32,6 +32,7 @@ #import "AMPConstants.h" #import "AMPDeviceInfo.h" #import "AMPURLConnection.h" +#import "AMPURLSession.h" #import "AMPDatabaseHelper.h" #import "AMPUtils.h" #import "AMPIdentify.h" @@ -964,14 +965,13 @@ - (void)makeEventUploadPostRequest:(NSString*) url events:(NSString*) events num SAFE_ARC_RELEASE(postData); -// // If pinning is enabled, use the AMPURLConnection that handles it. -//#if AMPLITUDE_SSL_PINNING -// id Connection = (self.sslPinningEnabled ? [AMPURLConnection class] : [NSURLConnection class]); -//#else -// id Connection = [NSURLConnection class]; -//#endif -// [Connection sendAsynchronousRequest:request queue:_backgroundQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { - [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + // If pinning is enabled, use the AMPURLSession that handles it. +#if AMPLITUDE_SSL_PINNING + id session = (self.sslPinningEnabled ? [AMPURLSession class] : [NSURLSession sharedSession]); +#else + id session = [NSURLSession sharedSession]; +#endif + [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { BOOL uploadSuccessful = NO; NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; if (response != nil) { diff --git a/AmplitudeTests/AmplitudeTVOSTests.m b/AmplitudeTests/AmplitudeTVOSTests.m index 4a49764c..ec21d4d8 100644 --- a/AmplitudeTests/AmplitudeTVOSTests.m +++ b/AmplitudeTests/AmplitudeTVOSTests.m @@ -29,28 +29,28 @@ @interface AmplitudeTVOSTests : BaseTestCase @end @implementation AmplitudeTVOSTests { - id _connectionMock; + id _sharedSessionMock; int _connectionCallCount; } - (void)setUp { [super setUp]; - _connectionMock = [OCMockObject mockForClass:NSURLConnection.class]; + _sharedSessionMock = [OCMockObject partialMockForObject:[NSURLSession sharedSession]]; _connectionCallCount = 0; [self.amplitude initializeApiKey:apiKey]; } - (void)tearDown { - [_connectionMock stopMocking]; + [_sharedSessionMock stopMocking]; } -- (void)setupAsyncResponse:(id) connectionMock response:(NSMutableDictionary*) serverResponse { - [[[connectionMock expect] andDo:^(NSInvocation *invocation) { +- (void)setupAsyncResponse: (NSMutableDictionary*) serverResponse { + [[[_sharedSessionMock stub] andDo:^(NSInvocation *invocation) { _connectionCallCount++; void (^handler)(NSURLResponse*, NSData*, NSError*); - [invocation getArgument:&handler atIndex:4]; - handler(serverResponse[@"response"], serverResponse[@"data"], serverResponse[@"error"]); - }] sendAsynchronousRequest:OCMOCK_ANY queue:OCMOCK_ANY completionHandler:OCMOCK_ANY]; + [invocation getArgument:&handler atIndex:3]; + handler(serverResponse[@"data"], serverResponse[@"response"], serverResponse[@"error"]); + }] dataTaskWithRequest:OCMOCK_ANY completionHandler:OCMOCK_ANY]; } - (void)testLogEventUploadLogic { @@ -58,7 +58,7 @@ - (void)testLogEventUploadLogic { @{ @"response" : [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"/"] statusCode:200 HTTPVersion:nil headerFields:@{}], @"data" : [@"bad_checksum" dataUsingEncoding:NSUTF8StringEncoding] }]; - [self setupAsyncResponse:_connectionMock response:serverResponse]; + [self setupAsyncResponse:serverResponse]; // tv os should have upload event upload threshold set to 1 XCTAssertEqual(kAMPEventUploadThreshold, 1); diff --git a/AmplitudeTests/SSLPinningTests.m b/AmplitudeTests/SSLPinningTests.m index 34f5be7f..da23185c 100644 --- a/AmplitudeTests/SSLPinningTests.m +++ b/AmplitudeTests/SSLPinningTests.m @@ -12,10 +12,10 @@ #import "Amplitude.h" #import "Amplitude+Test.h" #import "BaseTestCase.h" -#import "AMPURLConnection.h" +#import "AMPURLSession.h" #import "Amplitude+SSLPinning.h" -@interface AMPURLConnection (Test) +@interface AMPURLSession (Test) + (void)pinSSLCertificate:(NSArray *)certFilename; @@ -56,8 +56,10 @@ - (void)testSSLWithoutPinning { } - (void)testSSLPinningInvalidCert { + XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Pinning"]; + self.amplitude.sslPinningEnabled = YES; - [AMPURLConnection pinSSLCertificate:@[@"InvalidCertificationAuthority"]]; + [AMPURLSession pinSSLCertificate:@[@"InvalidCertificationAuthority"]]; [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; [self.amplitude logEvent:@"Test Invalid SSL Pinning"]; @@ -68,6 +70,13 @@ - (void)testSSLPinningInvalidCert { NSDictionary *event = [self.amplitude getLastEvent]; XCTAssertNotNil(event); XCTAssertEqual([self.databaseHelper getEventCount], 1); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) { + if (error) { + XCTFail(@"Expectation Failed with error: %@", error); + } }]; } @@ -75,7 +84,7 @@ - (void)testSSLPinningValidCert { XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Pinning"]; self.amplitude.sslPinningEnabled = YES; - [AMPURLConnection pinSSLCertificate:@[@"ComodoRsaCA", @"ComodoRsaDomainValidationCA"]]; + [AMPURLSession pinSSLCertificate:@[@"ComodoRsaCA", @"ComodoRsaDomainValidationCA"]]; [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; [self.amplitude logEvent:@"Test SSL Pinning"]; From 899a3dae172443f5688454660dc367bf812f890d Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Fri, 15 Sep 2017 13:50:44 -0700 Subject: [PATCH 5/6] maintain static instance with shared session --- Amplitude/AMPURLSession.h | 5 +++-- Amplitude/AMPURLSession.m | 30 +++++++++++++++++++++--------- Amplitude/Amplitude.m | 6 +++--- AmplitudeTests/Amplitude+Test.h | 2 +- AmplitudeTests/Amplitude+Test.m | 2 +- AmplitudeTests/SSLPinningTests.m | 1 + Podfile | 2 +- 7 files changed, 31 insertions(+), 17 deletions(-) diff --git a/Amplitude/AMPURLSession.h b/Amplitude/AMPURLSession.h index 1f9bb803..d736ac65 100644 --- a/Amplitude/AMPURLSession.h +++ b/Amplitude/AMPURLSession.h @@ -10,9 +10,10 @@ #import #import "ISPPinnedNSURLSessionDelegate.h" -@interface AMPURLSession : ISPPinnedNSURLSessionDelegate +@interface AMPURLSession : ISPPinnedNSURLSessionDelegate -+ (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; ++ (AMPURLSession *)sharedSession; +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler; @end #endif diff --git a/Amplitude/AMPURLSession.m b/Amplitude/AMPURLSession.m index fbe035df..1e4ea102 100644 --- a/Amplitude/AMPURLSession.m +++ b/Amplitude/AMPURLSession.m @@ -17,13 +17,27 @@ @interface AMPURLSession () @end -@implementation AMPURLSession +@implementation AMPURLSession { + NSURLSession *_sharedSession; +} + ++ (AMPURLSession *)sharedSession { + static AMPURLSession *_instance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _instance = [[AMPURLSession alloc] init]; + }); + return _instance; +} -+ (void)initialize +- (id)init { - if (self == [AMPURLSession class]) { + if ((self = [super init])) { [AMPURLSession pinSSLCertificate:@[@"ComodoRsaCA", @"ComodoRsaDomainValidationCA"]]; + NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; + _sharedSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; } + return self; } + (void)pinSSLCertificate:(NSArray *)certFilenames @@ -59,16 +73,14 @@ + (void)pinSSLCertificate:(NSArray *)certFilenames - (void)dealloc { + [_sharedSession finishTasksAndInvalidate]; + SAFE_ARC_RELEASE(_sharedSession); SAFE_ARC_SUPER_DEALLOC(); } -+ (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request +- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler { - - AMPURLSession *delegate = SAFE_ARC_AUTORELEASE([[AMPURLSession alloc] init]); - NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:delegate delegateQueue:nil]; - return [session dataTaskWithRequest:request completionHandler:completionHandler]; + return [_sharedSession dataTaskWithRequest:request completionHandler:completionHandler]; } @end diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index 21c1390a..d4166219 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -967,11 +967,11 @@ - (void)makeEventUploadPostRequest:(NSString*) url events:(NSString*) events num // If pinning is enabled, use the AMPURLSession that handles it. #if AMPLITUDE_SSL_PINNING - id session = (self.sslPinningEnabled ? [AMPURLSession class] : [NSURLSession sharedSession]); + id session = (self.sslPinningEnabled ? [AMPURLSession class] : [NSURLSession class]); #else - id session = [NSURLSession sharedSession]; + id session = [NSURLSession class]; #endif - [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + [[[session sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { BOOL uploadSuccessful = NO; NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; if (response != nil) { diff --git a/AmplitudeTests/Amplitude+Test.h b/AmplitudeTests/Amplitude+Test.h index 1b2d3e11..f35c92fb 100644 --- a/AmplitudeTests/Amplitude+Test.h +++ b/AmplitudeTests/Amplitude+Test.h @@ -22,7 +22,7 @@ - (void)flushQueue; - (void)flushQueueWithQueue:(NSOperationQueue*) queue; -- (void)flushUploads:(void (^)())handler; +- (void)flushUploads:(void (^)(void))handler; - (NSDictionary *)getLastEvent; - (NSDictionary *)getLastIdentify; - (NSDictionary *)getEvent:(NSInteger) fromEnd; diff --git a/AmplitudeTests/Amplitude+Test.m b/AmplitudeTests/Amplitude+Test.m index 6f97ecf7..d4781a3d 100644 --- a/AmplitudeTests/Amplitude+Test.m +++ b/AmplitudeTests/Amplitude+Test.m @@ -49,7 +49,7 @@ - (NSUInteger)queuedEventCount { return [[AMPDatabaseHelper getDatabaseHelper] getEventCount]; } -- (void)flushUploads:(void (^)())handler { +- (void)flushUploads:(void (^)(void))handler { [self performSelector:@selector(uploadEvents)]; [self flushQueue]; diff --git a/AmplitudeTests/SSLPinningTests.m b/AmplitudeTests/SSLPinningTests.m index da23185c..82159636 100644 --- a/AmplitudeTests/SSLPinningTests.m +++ b/AmplitudeTests/SSLPinningTests.m @@ -59,6 +59,7 @@ - (void)testSSLPinningInvalidCert { XCTestExpectation *expectation = [self expectationWithDescription:@"Testing Pinning"]; self.amplitude.sslPinningEnabled = YES; + [AMPURLSession sharedSession]; // trigger static instance to pin valid certificates, then pin the invalid one [AMPURLSession pinSSLCertificate:@[@"InvalidCertificationAuthority"]]; [self.amplitude initializeApiKey:@"1cc2c1978ebab0f6451112a8f5df4f4e"]; diff --git a/Podfile b/Podfile index 2aa39c32..24c2275f 100644 --- a/Podfile +++ b/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '6.0' +platform :ios, '9.0' project 'Amplitude' From 08ae8b0f3b8cdeebd436453c025683ff6f8a1b68 Mon Sep 17 00:00:00 2001 From: Daniel Jih Date: Mon, 18 Sep 2017 15:17:58 -0700 Subject: [PATCH 6/6] define explicit list of characters to encode --- Amplitude/Amplitude.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Amplitude/Amplitude.m b/Amplitude/Amplitude.m index d4166219..fb3830cb 100644 --- a/Amplitude/Amplitude.m +++ b/Amplitude/Amplitude.m @@ -1535,7 +1535,8 @@ - (NSString*)md5HexDigest:(NSString*)input - (NSString*)urlEncodeString:(NSString*) string { - return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; + NSCharacterSet * allowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:@":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"] invertedSet]; + return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacters]; } - (NSDate*) currentTime