diff options
Diffstat (limited to 'platform/ios/src/MGLAPIClient.m')
-rw-r--r-- | platform/ios/src/MGLAPIClient.m | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/platform/ios/src/MGLAPIClient.m b/platform/ios/src/MGLAPIClient.m new file mode 100644 index 0000000000..b4f85b5631 --- /dev/null +++ b/platform/ios/src/MGLAPIClient.m @@ -0,0 +1,195 @@ +#import "MGLAPIClient.h" +#import "NSBundle+MGLAdditions.h" +#import "MGLAccountManager.h" + +static NSString * const MGLAPIClientUserAgent = @"MapboxEventsiOS/1.1"; +static NSString * const MGLAPIClientBaseURL = @"https://api.tiles.mapbox.com"; + +static NSString * const MGLAPIClientHeaderFieldUserAgentKey = @"User-Agent"; +static NSString * const MGLAPIClientHeaderFieldContentTypeKey = @"Content-Type"; +static NSString * const MGLAPIClientHeaderFieldContentTypeValue = @"application/json"; +static NSString * const MGLAPIClientHTTPMethodPost = @"POST"; + +@interface MGLAPIClient () + +@property (nonatomic, copy) NSURLSession *session; +@property (nonatomic, copy) NSString *baseURL; +@property (nonatomic, copy) NSData *digicertCert; +@property (nonatomic, copy) NSData *geoTrustCert; +@property (nonatomic, copy) NSData *testServerCert; +@property (nonatomic, copy) NSString *userAgent; +@property (nonatomic) NSMutableArray *dataTasks; +@property (nonatomic) BOOL usesTestServer; + +@end + +@implementation MGLAPIClient + +- (instancetype)init { + self = [super init]; + if (self) { + _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] + delegate:self delegateQueue:nil]; + _dataTasks = [NSMutableArray array]; + [self loadCertificates]; + [self setupBaseURL]; + [self setupUserAgent]; + } + return self; +} + +#pragma mark Public API + +- (void)postEvents:(nonnull NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler { + NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:[self requestForEvents:events] + completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + [self.dataTasks removeObject:dataTask]; + if (completionHandler) { + completionHandler(error); + } + }]; + [dataTask resume]; + [self.dataTasks addObject:dataTask]; +} + +- (void)postEvent:(nonnull MGLMapboxEventAttributes *)event completionHandler:(nullable void (^)(NSError * _Nullable error))completionHandler { + [self postEvents:@[event] completionHandler:completionHandler]; +} + +- (void)cancelAll { + [self.dataTasks makeObjectsPerformSelector:@selector(cancel)]; + [self.dataTasks removeAllObjects]; +} + +#pragma mark Utilities + +- (NSURLRequest *)requestForEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events { + NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", self.baseURL, [MGLAccountManager accessToken]]; + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; + [request setValue:self.userAgent forHTTPHeaderField:MGLAPIClientHeaderFieldUserAgentKey]; + [request setValue:MGLAPIClientHeaderFieldContentTypeValue forHTTPHeaderField:MGLAPIClientHeaderFieldContentTypeKey]; + [request setHTTPMethod:MGLAPIClientHTTPMethodPost]; + NSData *jsonData = [self serializedDataForEvents:events]; + [request setHTTPBody:jsonData]; + return [request copy]; +} + +- (void)setupBaseURL { + NSString *testServerURL = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"MGLMetricsTestServerURL"]; + if (testServerURL) { + _baseURL = testServerURL; + _usesTestServer = YES; + } else { + _baseURL = MGLAPIClientBaseURL; + } +} + +- (void)loadCertificates { + NSData *certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_com-geotrust"]; + self.geoTrustCert = certificate; + [self loadCertificate:&certificate withResource:@"api_mapbox_com-digicert"]; + self.digicertCert = certificate; + [self loadCertificate:&certificate withResource:@"star_tilestream_net"]; + self.testServerCert = certificate; +} + +- (void)loadCertificate:(NSData **)certificate withResource:(NSString *)resource { + NSBundle *resourceBundle = [NSBundle mgl_frameworkBundle]; + NSString *cerPath = [resourceBundle pathForResource:resource ofType:@"der"]; + if (cerPath != nil) { + *certificate = [NSData dataWithContentsOfFile:cerPath]; + } +} + +- (void)setupUserAgent { + NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"]; + NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSString *appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; + _userAgent = [NSString stringWithFormat:@"%@/%@/%@ %@", appName, appVersion, appBuildNumber, MGLAPIClientUserAgent]; +} + +#pragma mark - JSON Serialization + +- (NSData *)serializedDataForEvents:(NS_ARRAY_OF(MGLMapboxEventAttributes *) *)events { + return [NSJSONSerialization dataWithJSONObject:events options:0 error:nil]; +} + +#pragma mark NSURLSessionDelegate + +- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { + + if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { + + SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust]; + SecTrustResultType trustResult; + + // Validate the certificate chain with the device's trust store anyway + // This *might* give use revocation checking + SecTrustEvaluate(serverTrust, &trustResult); + if (trustResult == kSecTrustResultUnspecified) + { + // Look for a pinned certificate in the server's certificate chain + long numKeys = SecTrustGetCertificateCount(serverTrust); + + BOOL found = NO; + // Try GeoTrust Cert First + for (int lc = 0; lc < numKeys; lc++) { + SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc); + NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate)); + + // Compare Remote Key With Local Version + if ([remoteCertificateData isEqualToData:_geoTrustCert]) { + // Found the certificate; continue connecting + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + found = YES; + break; + } + } + + if (!found) { + // Fallback to Digicert Cert + for (int lc = 0; lc < numKeys; lc++) { + SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc); + NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate)); + + // Compare Remote Key With Local Version + if ([remoteCertificateData isEqualToData:_digicertCert]) { + // Found the certificate; continue connecting + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + found = YES; + break; + } + } + + if (!found && _usesTestServer) { + // See if this is test server + for (int lc = 0; lc < numKeys; lc++) { + SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, lc); + NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate)); + + // Compare Remote Key With Local Version + if ([remoteCertificateData isEqualToData:_testServerCert]) { + // Found the certificate; continue connecting + completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + found = YES; + break; + } + } + } + + if (!found) { + // The certificate wasn't found in GeoTrust nor Digicert. Cancel the connection. + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + } + } + } + else + { + // Certificate chain validation failed; cancel the connection + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); + } + } +} + +@end |