// // Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // A copy of the License is located at // // http://aws.amazon.com/apache2.0 // // or in the "license" file accompanying this file. This file is distributed // on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either // express or implied. See the License for the specific language governing // permissions and limitations under the License. // #import "AWSNetworking.h" #import #import "AWSBolts.h" #import "AWSCategory.h" #import "AWSModel.h" #import "AWSURLSessionManager.h" #import "AWSService.h" NSString *const AWSNetworkingErrorDomain = @"com.amazonaws.AWSNetworkingErrorDomain"; #pragma mark - AWSHTTPMethod @implementation NSString (AWSHTTPMethod) + (instancetype)aws_stringWithHTTPMethod:(AWSHTTPMethod)HTTPMethod { NSString *string = nil; switch (HTTPMethod) { case AWSHTTPMethodGET: string = @"GET"; break; case AWSHTTPMethodHEAD: string = @"HEAD"; break; case AWSHTTPMethodPOST: string = @"POST"; break; case AWSHTTPMethodPUT: string = @"PUT"; break; case AWSHTTPMethodPATCH: string = @"PATCH"; break; case AWSHTTPMethodDELETE: string = @"DELETE"; break; default: break; } return string; } @end #pragma mark - AWSURLSessionManager @interface AWSURLSessionManager() - (void)invalidate; @end #pragma mark - AWSNetworking @interface AWSNetworking() @property (nonatomic, strong) AWSURLSessionManager *sessionManager; @end @implementation AWSNetworking - (instancetype)init { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"`- init` is not a valid initializer. Use `- initWithConfiguration` instead." userInfo:nil]; return nil; } - (instancetype)initWithConfiguration:(AWSNetworkingConfiguration *)configuration { if (self = [super init]) { _sessionManager = [[AWSURLSessionManager alloc] initWithConfiguration:configuration]; } return self; } - (AWSTask *)sendRequest:(AWSNetworkingRequest *)request { return [self.sessionManager dataTaskWithRequest:request]; } - (void)dealloc { // If this is being released, the network manager should be notified so it can invalidate // its NSURLSession to avoid a memory leak. [_sessionManager invalidate]; } @end #pragma mark - AWSNetworkingConfiguration @implementation AWSNetworkingConfiguration - (instancetype)init { if (self = [super init]) { _maxRetryCount = 3; _allowsCellularAccess = YES; } return self; } - (NSURL *)URL { // You can overwrite the URL by providing a full URL in URLString. NSURL *fullURL = [NSURL URLWithString:self.URLString]; if ([fullURL.scheme isEqualToString:@"http"] || [fullURL.scheme isEqualToString:@"https"]) { NSMutableDictionary *headers = [self.headers mutableCopy]; headers[@"Host"] = [fullURL host]; self.headers = headers; return fullURL; } if (!self.URLString) { return self.baseURL; } return [NSURL URLWithString:self.URLString relativeToURL:self.baseURL]; } - (void)setMaxRetryCount:(uint32_t)maxRetryCount { // the max maxRetryCount is 10. If set to higher than that, it becomes 10. if (maxRetryCount > 10) { _maxRetryCount = 10; } else { _maxRetryCount = maxRetryCount; } } - (id)copyWithZone:(NSZone *)zone { AWSNetworkingConfiguration *configuration = nil; if ([self isMemberOfClass:[AWSServiceConfiguration class]]) { configuration = [[AWSServiceConfiguration allocWithZone:zone] initWithRegion:AWSRegionUnknown credentialsProvider:nil]; } else { configuration = [[[self class] allocWithZone:zone] init]; } configuration.baseURL = [self.baseURL copy]; configuration.URLString = [self.URLString copy]; configuration.HTTPMethod = self.HTTPMethod; configuration.headers = [self.headers copy]; configuration.allowsCellularAccess = self.allowsCellularAccess; configuration.sharedContainerIdentifier = self.sharedContainerIdentifier; configuration.requestSerializer = self.requestSerializer; configuration.requestInterceptors = [self.requestInterceptors copy]; configuration.responseSerializer = self.responseSerializer; configuration.responseInterceptors = [self.responseInterceptors copy]; configuration.retryHandler = self.retryHandler; configuration.maxRetryCount = self.maxRetryCount; configuration.timeoutIntervalForRequest = self.timeoutIntervalForRequest; configuration.timeoutIntervalForResource = self.timeoutIntervalForResource; return configuration; } @end #pragma mark - AWSNetworkingRequest @interface AWSNetworkingRequest() @property (nonatomic, strong) NSURLSessionTask *task; @property (nonatomic, assign, getter = isCancelled) BOOL cancelled; @end @implementation AWSNetworkingRequest - (void)assignProperties:(AWSNetworkingConfiguration *)configuration { if (!self.baseURL) { self.baseURL = configuration.baseURL; } if (!self.URLString) { self.URLString = configuration.URLString; } if (!self.HTTPMethod) { self.HTTPMethod = configuration.HTTPMethod; } if (configuration.headers) { NSMutableDictionary *mutableCopy = [configuration.headers mutableCopy]; [self.headers enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { [mutableCopy setObject:obj forKey:key]; }]; self.headers = mutableCopy; } if (!self.requestSerializer) { self.requestSerializer = configuration.requestSerializer; } if (configuration.requestInterceptors) { self.requestInterceptors = configuration.requestInterceptors; } if (!self.responseSerializer) { self.responseSerializer = configuration.responseSerializer; } if (configuration.responseInterceptors) { self.responseInterceptors = configuration.responseInterceptors; } if (!self.retryHandler) { self.retryHandler = configuration.retryHandler; } } - (void)setTask:(NSURLSessionTask *)task { @synchronized(self) { if (!_cancelled) { _task = task; } else { _task = nil; } } } - (BOOL)isCancelled { @synchronized(self) { return _cancelled; } } - (void)cancel { @synchronized(self) { if (!_cancelled) { _cancelled = YES; [self.task cancel]; } } } - (void)pause { @synchronized(self) { [self.task cancel]; } } @end @interface AWSRequest() @property (nonatomic, strong) AWSNetworkingRequest *internalRequest; @property (nonatomic, assign) NSNumber *shouldWriteDirectly; @end @implementation AWSRequest + (BOOL)supportsSecureCoding { return YES; } + (NSDictionary *)encodingBehaviorsByPropertyKey { NSMutableDictionary *encodingBehaviors = [[super encodingBehaviorsByPropertyKey] mutableCopy]; encodingBehaviors[@"downloadingFileURL"] = @(AWSMTLModelEncodingBehaviorUnconditional); encodingBehaviors[@"shouldWriteDirectly"] = @(AWSMTLModelEncodingBehaviorUnconditional); encodingBehaviors[@"downloadProgress"] = @(AWSMTLModelEncodingBehaviorExcluded); encodingBehaviors[@"internalRequest"] = @(AWSMTLModelEncodingBehaviorExcluded); encodingBehaviors[@"uploadProgress"] = @(AWSMTLModelEncodingBehaviorExcluded); return encodingBehaviors; } - (instancetype)init { if (self = [super init]) { _internalRequest = [AWSNetworkingRequest new]; } return self; } // This may be a bug in our version of Mantle--despite declaring these properties as "excluded", // Mantle attempts to decode them from an archive, and fails when it cannot find the field name. - (nullable id)decodeDownloadProgressWithCoder:(NSCoder *)coder modelVersion:(NSUInteger)modelVersion { return NULL; } // This may be a bug in our version of Mantle--despite declaring these properties as "excluded", // Mantle attempts to decode them from an archive, and fails when it cannot find the field name. - (nullable id)decodeInternalRequestWithCoder:(NSCoder *)coder modelVersion:(NSUInteger)modelVersion { return NULL; } // This may be a bug in our version of Mantle--despite declaring these properties as "excluded", // Mantle attempts to decode them from an archive, and fails when it cannot find the field name. - (nullable id)decodeUploadProgressWithCoder:(NSCoder *)coder modelVersion:(NSUInteger)modelVersion { return NULL; } - (void)setUploadProgress:(AWSNetworkingUploadProgressBlock)uploadProgress { self.internalRequest.uploadProgress = uploadProgress; } - (void)setDownloadProgress:(AWSNetworkingDownloadProgressBlock)downloadProgress { self.internalRequest.downloadProgress = downloadProgress; } - (BOOL)isCancelled { return [self.internalRequest isCancelled]; } - (AWSTask *)cancel { [self.internalRequest cancel]; return [AWSTask taskWithResult:nil]; } - (AWSTask *)pause { [self.internalRequest pause]; return [AWSTask taskWithResult:nil]; } - (NSDictionary *)dictionaryValue { NSDictionary *dictionaryValue = [super dictionaryValue]; NSMutableDictionary *mutableDictionaryValue = [dictionaryValue mutableCopy]; [dictionaryValue enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { if ([key isEqualToString:@"internalRequest"]) { [mutableDictionaryValue removeObjectForKey:key]; } }]; return mutableDictionaryValue; } @end @interface AWSNetworkingRequestInterceptor() @property (nonatomic, strong) NSString *userAgent; @end @implementation AWSNetworkingRequestInterceptor - (instancetype)init { if (self = [super init]) { _userAgent = [AWSServiceConfiguration baseUserAgent]; } return self; } - (instancetype)initWithUserAgent:(NSString *)userAgent { if (self = [super init]) { _userAgent = userAgent; } return self; } - (AWSTask *)interceptRequest:(NSMutableURLRequest *)request { [request setValue:[[NSDate aws_clockSkewFixedDate] aws_stringValue:AWSDateISO8601DateFormat2] forHTTPHeaderField:@"X-Amz-Date"]; [request setValue:self.userAgent forHTTPHeaderField:@"User-Agent"]; return [AWSTask taskWithResult:nil]; } @end