diff --git a/AFNetworking.podspec b/AFNetworking.podspec index 8c6affc..c3a8dbb 100644 --- a/AFNetworking.podspec +++ b/AFNetworking.podspec @@ -1,22 +1,25 @@ Pod::Spec.new do |s| s.name = 'AFNetworking' - s.version = '1.1.0' + s.version = '1.2.0' s.license = 'MIT' s.summary = 'A delightful iOS and OS X networking framework.' s.homepage = 'https://github.com/AFNetworking/AFNetworking' s.authors = { 'Mattt Thompson' => 'm@mattt.me', 'Scott Raymond' => 'sco@gowalla.com' } - s.source = { :git => 'https://github.com/AFNetworking/AFNetworking.git', :tag => '1.1.0' } + s.source = { :git => 'https://github.com/AFNetworking/AFNetworking.git', :tag => '1.2.0' } s.source_files = 'AFNetworking' s.requires_arc = true s.ios.deployment_target = '5.0' - s.ios.frameworks = 'MobileCoreServices', 'SystemConfiguration' + s.ios.frameworks = 'MobileCoreServices', 'SystemConfiguration', 'Security' s.osx.deployment_target = '10.7' - s.osx.frameworks = 'CoreServices', 'SystemConfiguration' + s.osx.frameworks = 'CoreServices', 'SystemConfiguration', 'Security' s.prefix_header_contents = <<-EOS #import + +#define _AFNETWORKING_PIN_SSL_CERTIFICATES_ + #if __IPHONE_OS_VERSION_MIN_REQUIRED #import #import diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index d1c6f73..5551f5d 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -21,6 +21,7 @@ // THE SOFTWARE. #import +#import "AFURLConnectionOperation.h" #import @@ -137,6 +138,13 @@ typedef enum { @property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; #endif +/** + Default SSL pinning mode for each `AFHTTPRequestOperation` which will be enqueued with `enqueueHTTPRequestOperation:`. + */ +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ +@property (nonatomic, assign) AFURLConnectionOperationSSLPinningMode defaultSSLPinningMode; +#endif + ///--------------------------------------------- /// @name Creating and Initializing HTTP Clients ///--------------------------------------------- @@ -440,7 +448,7 @@ typedef enum { ///---------------- /** - ### Network Reachability + ## Network Reachability The following constants are provided by `AFHTTPClient` as possible network reachability statuses. @@ -471,7 +479,7 @@ typedef enum { A key in the userInfo dictionary in a `AFNetworkingReachabilityDidChangeNotification` notification. The corresponding value is an `NSNumber` object representing the `AFNetworkReachabilityStatus` value for the current reachability status. - ### Parameter Encoding + ## Parameter Encoding The following constants are provided by `AFHTTPClient` as possible methods for serializing parameters into query string or message body values. @@ -543,12 +551,44 @@ extern NSTimeInterval const kAFUploadStream3GSuggestedDelay; @return `YES` if the file data was successfully appended, otherwise `NO`. - @discussion The filename and MIME type for this data in the form will be automatically generated, using `NSURLResponse` `-suggestedFilename` and `-MIMEType`, respectively. + @discussion The filename and MIME type for this data in the form will be automatically generated, using the last path component of the `fileURL` and system associated MIME type for the `fileURL` extension, respectively. */ - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError * __autoreleasing *)error; +/** + Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary. + + @param fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`. + @param name The name to be associated with the specified data. This parameter must not be `nil`. + @param fileName The file name to be used in the `Content-Disposition` header. This parameter must not be `nil`. + @param mimeType The declared MIME type of the file data. This parameter must not be `nil`. + @param error If an error occurs, upon return contains an `NSError` object that describes the problem. + + @return `YES` if the file data was successfully appended otherwise `NO`. + */ +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL + name:(NSString *)name + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType + error:(NSError * __autoreleasing *)error; + +/** + Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the data from the input stream and the multipart form boundary. + + @param inputStream The input stream to be appended to the form data + @param name The name to be associated with the specified input stream. This parameter must not be `nil`. + @param fileName The filename to be associated with the specified input stream. This parameter must not be `nil`. + @param length The length of the specified input stream in bytes. + @param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`. + */ +- (void)appendPartWithInputStream:(NSInputStream *)inputStream + name:(NSString *)name + fileName:(NSString *)fileName + length:(unsigned long long)length + mimeType:(NSString *)mimeType; + /** Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file data and the multipart form boundary. @@ -572,6 +612,7 @@ extern NSTimeInterval const kAFUploadStream3GSuggestedDelay; - (void)appendPartWithFormData:(NSData *)data name:(NSString *)name; + /** Appends HTTP headers, followed by the encoded data and the multipart form boundary. diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index c13a524..bd0cab2 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -81,7 +81,7 @@ static NSString * AFBase64EncodedStringFromString(NSString *string) { } static NSString * AFPercentEscapedQueryStringPairMemberFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { - static NSString * const kAFCharactersToBeEscaped = @":/?&=;+!@#$()~'"; + static NSString * const kAFCharactersToBeEscaped = @":/?&=;+!@#$()~',*"; static NSString * const kAFCharactersToLeaveUnescaped = @"[]."; return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, (__bridge CFStringRef)kAFCharactersToLeaveUnescaped, (__bridge CFStringRef)kAFCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)); @@ -146,10 +146,11 @@ NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dictionary = value; // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(caseInsensitiveCompare:)]; - [[[value allKeys] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]] enumerateObjectsUsingBlock:^(id nestedKey, __unused NSUInteger idx, __unused BOOL *stop) { - id nestedValue = [value objectForKey:nestedKey]; + [[[dictionary allKeys] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]] enumerateObjectsUsingBlock:^(id nestedKey, __unused NSUInteger idx, __unused BOOL *stop) { + id nestedValue = [dictionary objectForKey:nestedKey]; if (nestedValue) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; } @@ -159,6 +160,11 @@ NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { [array enumerateObjectsUsingBlock:^(id nestedValue, __unused NSUInteger idx, __unused BOOL *stop) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; }]; + } else if ([value isKindOfClass:[NSSet class]]) { + NSSet *set = value; + [set enumerateObjectsUsingBlock:^(id obj, BOOL *stop) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)]; + }]; } else { [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]]; } @@ -234,9 +240,14 @@ NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { self.defaultHeaders = [NSMutableDictionary dictionary]; // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 - NSString *preferredLanguageCodes = [[NSLocale preferredLanguages] componentsJoinedByString:@", "]; - [self setDefaultHeader:@"Accept-Language" value:[NSString stringWithFormat:@"%@, en-us;q=0.8", preferredLanguageCodes]]; - + NSMutableArray *acceptLanguagesComponents = [NSMutableArray array]; + [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + float q = 1.0f - (idx * 0.1f); + [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]]; + *stop = q <= 0.5f; + }]; + [self setDefaultHeader:@"Accept-Language" value:[acceptLanguagesComponents componentsJoinedByString:@", "]]; + #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], (__bridge id)CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), kCFBundleVersionKey) ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0f)]]; @@ -304,7 +315,8 @@ static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused targ } dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:status] forKey:AFNetworkingReachabilityNotificationStatusItem]]; + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:status] forKey:AFNetworkingReachabilityNotificationStatusItem]]; }); } @@ -505,12 +517,12 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { AFHTTPRequestOperation *operation = nil; - NSString *className = nil; - NSEnumerator *enumerator = [self.registeredHTTPOperationClassNames reverseObjectEnumerator]; - while (!operation && (className = [enumerator nextObject])) { - Class op_class = NSClassFromString(className); - if (op_class && [op_class canProcessRequest:urlRequest]) { - operation = [(AFHTTPRequestOperation *)[op_class alloc] initWithRequest:urlRequest]; + + for (NSString *className in self.registeredHTTPOperationClassNames) { + Class operationClass = NSClassFromString(className); + if (operationClass && [operationClass canProcessRequest:urlRequest]) { + operation = [(AFHTTPRequestOperation *)[operationClass alloc] initWithRequest:urlRequest]; + break; } } @@ -521,6 +533,9 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { [operation setCompletionBlockWithSuccess:success failure:failure]; operation.credential = self.defaultCredential; +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ + operation.SSLPinningMode = self.defaultSSLPinningMode; +#endif return operation; } @@ -534,7 +549,7 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { - (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path { - NSString *URLStringToMatched = [[[self requestWithMethod:(method ?: @"GET") path:path parameters:nil] URL] absoluteString]; + NSString *pathToBeMatched = [[[self requestWithMethod:(method ?: @"GET") path:path parameters:nil] URL] path]; for (NSOperation *operation in [self.operationQueue operations]) { if (![operation isKindOfClass:[AFHTTPRequestOperation class]]) { @@ -542,9 +557,9 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { } BOOL hasMatchingMethod = !method || [method isEqualToString:[[(AFHTTPRequestOperation *)operation request] HTTPMethod]]; - BOOL hasMatchingURL = [[[[(AFHTTPRequestOperation *)operation request] URL] absoluteString] isEqualToString:URLStringToMatched]; + BOOL hasMatchingPath = [[[[(AFHTTPRequestOperation *)operation request] URL] path] isEqual:pathToBeMatched]; - if (hasMatchingMethod && hasMatchingURL) { + if (hasMatchingMethod && hasMatchingPath) { [operation cancel]; } } @@ -581,9 +596,10 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) { for (AFHTTPRequestOperation *operation in operations) { AFCompletionBlock originalCompletionBlock = [operation.completionBlock copy]; - __weak AFHTTPRequestOperation *weakOperation = operation; + __weak __typeof(&*operation)weakOperation = operation; operation.completionBlock = ^{ - dispatch_queue_t queue = weakOperation.successCallbackQueue ?: dispatch_get_main_queue(); + __strong __typeof(&*weakOperation)strongOperation = weakOperation; + dispatch_queue_t queue = strongOperation.successCallbackQueue ?: dispatch_get_main_queue(); dispatch_group_async(dispatchGroup, queue, ^{ if (originalCompletionBlock) { originalCompletionBlock(); @@ -729,7 +745,12 @@ static inline NSString * AFMultipartFormFinalBoundary() { static inline NSString * AFContentTypeForPathExtension(NSString *extension) { #ifdef __UTTYPE__ NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); - return (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); + NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); + if (!contentType) { + return @"application/octet-stream"; + } else { + return contentType; + } #else return @"application/octet-stream"; #endif @@ -755,9 +776,10 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; maxLength:(NSUInteger)length; @end -@interface AFMultipartBodyStream : NSInputStream -@property (nonatomic, assign) NSUInteger numberOfBytesInPacket; +@interface AFMultipartBodyStreamProvider : NSObject +@property (nonatomic, assign) NSUInteger bufferLength; @property (nonatomic, assign) NSTimeInterval delay; +@property (nonatomic, readonly) NSInputStream *inputStream; @property (nonatomic, readonly) unsigned long long contentLength; @property (nonatomic, readonly, getter = isEmpty) BOOL empty; @@ -770,7 +792,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; @interface AFStreamingMultipartFormData () @property (readwrite, nonatomic, copy) NSMutableURLRequest *request; -@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream; +@property (readwrite, nonatomic, strong) AFMultipartBodyStreamProvider *bodyStream; @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; @end @@ -789,7 +811,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; self.request = urlRequest; self.stringEncoding = encoding; - self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding]; + self.bodyStream = [[AFMultipartBodyStreamProvider alloc] initWithStringEncoding:encoding]; return self; } @@ -801,6 +823,23 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; NSParameterAssert(fileURL); NSParameterAssert(name); + NSString *fileName = [fileURL lastPathComponent]; + NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]); + + return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error]; +} + +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL + name:(NSString *)name + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType + error:(NSError * __autoreleasing *)error +{ + NSParameterAssert(fileURL); + NSParameterAssert(name); + NSParameterAssert(fileName); + NSParameterAssert(mimeType); + if (![fileURL isFileURL]) { NSDictionary *userInfo = [NSDictionary dictionaryWithObject:NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil) forKey:NSLocalizedFailureReasonErrorKey]; if (error != NULL) { @@ -818,8 +857,8 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; } NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; - [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, [fileURL lastPathComponent]] forKey:@"Content-Disposition"]; - [mutableHeaders setValue:AFContentTypeForPathExtension([fileURL pathExtension]) forKey:@"Content-Type"]; + [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; + [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; @@ -834,6 +873,32 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; return YES; } + +- (void)appendPartWithInputStream:(NSInputStream *)inputStream + name:(NSString *)name + fileName:(NSString *)fileName + length:(unsigned long long)length + mimeType:(NSString *)mimeType +{ + NSParameterAssert(name); + NSParameterAssert(fileName); + NSParameterAssert(mimeType); + + NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; + [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; + [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; + + + AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; + bodyPart.stringEncoding = self.stringEncoding; + bodyPart.headers = mutableHeaders; + bodyPart.body = inputStream; + + bodyPart.bodyContentLength = length; + + [self.bodyStream appendHTTPBodyPart:bodyPart]; +} + - (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName @@ -878,7 +943,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; - (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay { - self.bodyStream.numberOfBytesInPacket = numberOfBytes; + self.bodyStream.bufferLength = numberOfBytes; self.bodyStream.delay = delay; } @@ -892,7 +957,7 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"]; [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"]; - [self.request setHTTPBodyStream:self.bodyStream]; + [self.request setHTTPBodyStream:self.bodyStream.inputStream]; return self.request; } @@ -901,24 +966,31 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; #pragma mark - -@interface AFMultipartBodyStream () -@property (nonatomic, assign) NSStreamStatus streamStatus; -@property (nonatomic, strong) NSError *streamError; - +@interface AFMultipartBodyStreamProvider () @property (nonatomic, assign) NSStringEncoding stringEncoding; @property (nonatomic, strong) NSMutableArray *HTTPBodyParts; @property (nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator; @property (nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart; +@property (nonatomic, strong) NSInputStream *inputStream; +@property (nonatomic, strong) NSOutputStream *outputStream; +@property (nonatomic, strong) NSMutableData *buffer; @end -@implementation AFMultipartBodyStream -@synthesize streamStatus = _streamStatus; -@synthesize streamError = _streamError; +static const NSUInteger AFMultipartBodyStreamProviderDefaultBufferLength = 4096; + +@implementation AFMultipartBodyStreamProvider { +@private + // Workaround for stream delegates being weakly referenced, but otherwise unowned + __strong id _self; +} @synthesize stringEncoding = _stringEncoding; @synthesize HTTPBodyParts = _HTTPBodyParts; @synthesize HTTPBodyPartEnumerator = _HTTPBodyPartEnumerator; @synthesize currentHTTPBodyPart = _currentHTTPBodyPart; -@synthesize numberOfBytesInPacket = _numberOfBytesInPacket; +@synthesize inputStream = _inputStream; +@synthesize outputStream = _outputStream; +@synthesize buffer = _buffer; +@synthesize bufferLength = _numberOfBytesInPacket; @synthesize delay = _delay; - (id)initWithStringEncoding:(NSStringEncoding)encoding { @@ -929,11 +1001,18 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; self.stringEncoding = encoding; self.HTTPBodyParts = [NSMutableArray array]; - self.numberOfBytesInPacket = NSIntegerMax; - + self.bufferLength = NSIntegerMax; + + self.buffer = [[NSMutableData alloc] init]; + self.bufferLength = AFMultipartBodyStreamProviderDefaultBufferLength; + return self; } +- (void)dealloc { + _outputStream.delegate = nil; +} + - (void)setInitialAndFinalBoundaries { if ([self.HTTPBodyParts count] > 0) { for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { @@ -950,80 +1029,95 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; [self.HTTPBodyParts addObject:bodyPart]; } +- (NSInputStream *)inputStream { + if (_inputStream == nil) { + CFReadStreamRef readStream; + CFWriteStreamRef writeStream; + CFStreamCreateBoundPair(NULL, &readStream, &writeStream, self.bufferLength); + _inputStream = CFBridgingRelease(readStream); + _outputStream = CFBridgingRelease(writeStream); + + _outputStream.delegate = self; + if ([NSThread isMainThread]) { + [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + } else { + dispatch_sync(dispatch_get_main_queue(), ^{ + [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + }); + } + [_outputStream open]; + + _self = self; + } + + return _inputStream; +} + - (BOOL)isEmpty { return [self.HTTPBodyParts count] == 0; } -#pragma mark - NSInputStream +#pragma mark - NSStreamDelegate -- (NSInteger)read:(uint8_t *)buffer - maxLength:(NSUInteger)length +- (void)stream:(NSStream *)stream + handleEvent:(NSStreamEvent)eventCode { - if ([self streamStatus] == NSStreamStatusClosed) { - return 0; + if (eventCode & NSStreamEventHasSpaceAvailable) { + [self handleOutputStreamSpaceAvailable]; } - NSInteger bytesRead = 0; +} - while ((NSUInteger)bytesRead < MIN(length, self.numberOfBytesInPacket)) { - if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { - if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { - break; +- (void)handleOutputStreamSpaceAvailable { + while ([_outputStream hasSpaceAvailable]) { + if ([_buffer length] > 0) { + NSInteger numberOfBytesWritten = [_outputStream write:[_buffer bytes] maxLength:[_buffer length]]; + if (numberOfBytesWritten < 0) { + [self close]; + return; } + + [_buffer replaceBytesInRange:NSMakeRange(0, numberOfBytesWritten) withBytes:NULL length:0]; } else { - bytesRead += [self.currentHTTPBodyPart read:&buffer[bytesRead] maxLength:(length - (NSUInteger)bytesRead)]; + if (!self.currentHTTPBodyPart) { + if (!self.HTTPBodyPartEnumerator) { + self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator]; + } + self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject]; + } + + if (!self.currentHTTPBodyPart) { + [self close]; + return; + } + + [_buffer setLength:self.bufferLength]; + + NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:[_buffer mutableBytes] maxLength:[_buffer length]]; + if (numberOfBytesRead < 0) { + [self close]; + return; + } + + [_buffer setLength:numberOfBytesRead]; + + if(numberOfBytesRead == 0) { + self.currentHTTPBodyPart = nil; + } + if (self.delay > 0.0f) { [NSThread sleepForTimeInterval:self.delay]; } } } - return bytesRead; -} - -- (BOOL)getBuffer:(__unused uint8_t **)buffer - length:(__unused NSUInteger *)len -{ - return NO; -} - -- (BOOL)hasBytesAvailable { - return [self streamStatus] == NSStreamStatusOpen; -} - -#pragma mark - NSStream - -- (void)open { - if (self.streamStatus == NSStreamStatusOpen) { - return; - } - - self.streamStatus = NSStreamStatusOpen; - - [self setInitialAndFinalBoundaries]; - self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator]; } - (void)close { - self.streamStatus = NSStreamStatusClosed; + [_outputStream close]; + _outputStream.delegate = nil; + + _self = nil; } -- (id)propertyForKey:(__unused NSString *)key { - return nil; -} - -- (BOOL)setProperty:(__unused id)property - forKey:(__unused NSString *)key -{ - return NO; -} - -- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop - forMode:(__unused NSString *)mode -{} - -- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop - forMode:(__unused NSString *)mode -{} - - (unsigned long long)contentLength { unsigned long long length = 0; for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { @@ -1033,26 +1127,10 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; return length; } -#pragma mark - Undocumented CFReadStream Bridged Methods - -- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop - forMode:(__unused CFStringRef)aMode -{} - -- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop - forMode:(__unused CFStringRef)aMode -{} - -- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags - callback:(__unused CFReadStreamClientCallBack)inCallback - context:(__unused CFStreamClientContext *)inContext { - return NO; -} - #pragma mark - NSCopying --(id)copyWithZone:(NSZone *)zone { - AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding]; +- (id)copyWithZone:(NSZone *)zone { + AFMultipartBodyStreamProvider *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding]; for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { [bodyStreamCopy appendHTTPBodyPart:[bodyPart copy]]; @@ -1068,10 +1146,12 @@ NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; #pragma mark - typedef enum { + AFInitialPhase = 0, AFEncapsulationBoundaryPhase = 1, AFHeaderPhase = 2, AFBodyPhase = 3, AFFinalBoundaryPhase = 4, + AFCompletedPhase = 5, } AFHTTPBodyPartReadPhase; @interface AFHTTPBodyPart () { @@ -1118,6 +1198,8 @@ typedef enum { _inputStream = [NSInputStream inputStreamWithData:self.body]; } else if ([self.body isKindOfClass:[NSURL class]]) { _inputStream = [NSInputStream inputStreamWithURL:self.body]; + } else if ([self.body isKindOfClass:[NSInputStream class]]) { + _inputStream = self.body; } } @@ -1152,11 +1234,13 @@ typedef enum { } - (BOOL)hasBytesAvailable { - // Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` doesn't fit into the avaiable buffer + // Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` doesn't fit into the available buffer if (_phase == AFFinalBoundaryPhase) { return YES; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcovered-switch-default" switch (self.inputStream.streamStatus) { case NSStreamStatusNotOpen: case NSStreamStatusOpening: @@ -1170,6 +1254,8 @@ typedef enum { default: return NO; } +#pragma clang diagnostic pop + } - (NSInteger)read:(uint8_t *)buffer @@ -1227,7 +1313,12 @@ typedef enum { return YES; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcovered-switch-default" switch (_phase) { + case AFInitialPhase: + _phase = AFEncapsulationBoundaryPhase; + break; case AFEncapsulationBoundaryPhase: _phase = AFHeaderPhase; break; @@ -1241,10 +1332,13 @@ typedef enum { _phase = AFFinalBoundaryPhase; break; case AFFinalBoundaryPhase: - _phase = AFEncapsulationBoundaryPhase; + case AFCompletedPhase: + default: + _phase = AFCompletedPhase; break; } _phaseReadOffset = 0; +#pragma clang diagnostic pop return YES; } diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index d9cca8e..98aa07c 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -30,7 +30,6 @@ #define AF_CAST_TO_BLOCK __bridge void * #endif -// We do a little bit of duck typing in this file which can trigger this warning. Turn it off for this source file. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wstrict-selector-match" @@ -108,18 +107,12 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL @property (readwrite, nonatomic, strong) NSURLRequest *request; @property (readwrite, nonatomic, strong) NSHTTPURLResponse *response; @property (readwrite, nonatomic, strong) NSError *HTTPError; -@property (readwrite, nonatomic, copy) NSString *HTTPResponseString; -@property (readwrite, nonatomic, assign) long long totalContentLength; -@property (readwrite, nonatomic, assign) long long offsetContentLength; @end @implementation AFHTTPRequestOperation @synthesize HTTPError = _HTTPError; -@synthesize HTTPResponseString = _HTTPResponseString; @synthesize successCallbackQueue = _successCallbackQueue; @synthesize failureCallbackQueue = _failureCallbackQueue; -@synthesize totalContentLength = _totalContentLength; -@synthesize offsetContentLength = _offsetContentLength; @dynamic request; @dynamic response; @@ -169,23 +162,19 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL } } -- (NSString *)responseString { +- (NSStringEncoding)responseStringEncoding { // When no explicit charset parameter is provided by the sender, media subtypes of the "text" type are defined to have a default charset value of "ISO-8859-1" when received via HTTP. Data in character sets other than "ISO-8859-1" or its subsets MUST be labeled with an appropriate charset value. // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.4.1 - if (!self.HTTPResponseString && self.response && !self.response.textEncodingName && self.responseData) { + if (self.response && !self.response.textEncodingName && self.responseData) { NSString *type = nil; AFGetMediaTypeAndSubtypeWithString([[self.response allHeaderFields] valueForKey:@"Content-Type"], &type, nil); if ([type isEqualToString:@"text"]) { - self.HTTPResponseString = [[NSString alloc] initWithData:self.responseData encoding:NSISOLatin1StringEncoding]; + return NSISOLatin1StringEncoding; } } - if (self.HTTPResponseString) { - return self.HTTPResponseString; - } else { - return [super responseString]; - } + return [super responseStringEncoding]; } - (void)pause { @@ -324,46 +313,6 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL return [[self acceptableContentTypes] intersectsSet:AFContentTypesFromHTTPHeader([request valueForHTTPHeaderField:@"Accept"])]; } -#pragma mark - NSURLConnectionDelegate - -- (void)connection:(__unused NSURLConnection *)connection -didReceiveResponse:(NSURLResponse *)response -{ - self.response = (NSHTTPURLResponse *)response; - - // Set Content-Range header if status code of response is 206 (Partial Content) - // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.7 - long long totalContentLength = self.response.expectedContentLength; - long long fileOffset = 0; - NSUInteger statusCode = ([self.response isKindOfClass:[NSHTTPURLResponse class]]) ? (NSUInteger)[self.response statusCode] : 200; - if (statusCode == 206) { - NSString *contentRange = [self.response.allHeaderFields valueForKey:@"Content-Range"]; - if ([contentRange hasPrefix:@"bytes"]) { - NSArray *byteRanges = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]]; - if ([byteRanges count] == 4) { - fileOffset = [[byteRanges objectAtIndex:1] longLongValue]; - totalContentLength = [[byteRanges objectAtIndex:2] longLongValue] ?: -1; // if this is "*", it's converted to 0, but -1 is default. - } - } - } else { - if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { - [self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey]; - } else { - if ([[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length] > 0) { - self.outputStream = [NSOutputStream outputStreamToMemory]; - - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - for (NSString *runLoopMode in self.runLoopModes) { - [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; - } - } - } - } - - self.offsetContentLength = MAX(fileOffset, 0); - self.totalContentLength = totalContentLength; - - [self.outputStream open]; -} - @end + +#pragma clang diagnostic pop diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index e39c9d6..0a0390f 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -22,11 +22,12 @@ #import "AFImageRequestOperation.h" -static dispatch_queue_t af_image_request_operation_processing_queue; static dispatch_queue_t image_request_operation_processing_queue() { - if (af_image_request_operation_processing_queue == NULL) { - af_image_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.image-request.processing", 0); - } + static dispatch_queue_t af_image_request_operation_processing_queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + af_image_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.image-request.processing", DISPATCH_QUEUE_CONCURRENT); + }); return af_image_request_operation_processing_queue; } diff --git a/AFNetworking/AFJSONRequestOperation.m b/AFNetworking/AFJSONRequestOperation.m index bb893b5..008aacc 100644 --- a/AFNetworking/AFJSONRequestOperation.m +++ b/AFNetworking/AFJSONRequestOperation.m @@ -22,11 +22,12 @@ #import "AFJSONRequestOperation.h" -static dispatch_queue_t af_json_request_operation_processing_queue; static dispatch_queue_t json_request_operation_processing_queue() { - if (af_json_request_operation_processing_queue == NULL) { - af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.json-request.processing", 0); - } + static dispatch_queue_t af_json_request_operation_processing_queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.json-request.processing", DISPATCH_QUEUE_CONCURRENT); + }); return af_json_request_operation_processing_queue; } @@ -34,12 +35,14 @@ static dispatch_queue_t json_request_operation_processing_queue() { @interface AFJSONRequestOperation () @property (readwrite, nonatomic, strong) id responseJSON; @property (readwrite, nonatomic, strong) NSError *JSONError; +@property (readwrite, nonatomic, strong) NSRecursiveLock *lock; @end @implementation AFJSONRequestOperation @synthesize responseJSON = _responseJSON; @synthesize JSONReadingOptions = _JSONReadingOptions; @synthesize JSONError = _JSONError; +@dynamic lock; + (instancetype)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success @@ -61,6 +64,7 @@ static dispatch_queue_t json_request_operation_processing_queue() { - (id)responseJSON { + [self.lock lock]; if (!_responseJSON && [self.responseData length] > 0 && [self isFinished] && !self.JSONError) { NSError *error = nil; @@ -77,6 +81,7 @@ static dispatch_queue_t json_request_operation_processing_queue() { self.JSONError = error; } + [self.lock unlock]; return _responseJSON; } diff --git a/AFNetworking/AFNetworkActivityIndicatorManager.m b/AFNetworking/AFNetworkActivityIndicatorManager.m index 5b9ec82..b7e4256 100644 --- a/AFNetworking/AFNetworkActivityIndicatorManager.m +++ b/AFNetworking/AFNetworkActivityIndicatorManager.m @@ -62,8 +62,8 @@ static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.17; return nil; } - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(incrementActivityCount) name:AFNetworkingOperationDidStartNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(decrementActivityCount) name:AFNetworkingOperationDidFinishNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkingOperationDidStart:) name:AFNetworkingOperationDidStartNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkingOperationDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil]; return self; } @@ -126,6 +126,20 @@ static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.17; [self updateNetworkActivityIndicatorVisibilityDelayed]; } +- (void)networkingOperationDidStart:(NSNotification *)notification { + AFURLConnectionOperation *connectionOperation = [notification object]; + if (connectionOperation.request.URL) { + [self incrementActivityCount]; + } +} + +- (void)networkingOperationDidFinish:(NSNotification *)notification { + AFURLConnectionOperation *connectionOperation = [notification object]; + if (connectionOperation.request.URL) { + [self decrementActivityCount]; + } +} + @end #endif diff --git a/AFNetworking/AFPropertyListRequestOperation.m b/AFNetworking/AFPropertyListRequestOperation.m index 38229ce..4d6123b 100644 --- a/AFNetworking/AFPropertyListRequestOperation.m +++ b/AFNetworking/AFPropertyListRequestOperation.m @@ -22,11 +22,12 @@ #import "AFPropertyListRequestOperation.h" -static dispatch_queue_t af_property_list_request_operation_processing_queue; static dispatch_queue_t property_list_request_operation_processing_queue() { - if (af_property_list_request_operation_processing_queue == NULL) { - af_property_list_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.property-list-request.processing", 0); - } + static dispatch_queue_t af_property_list_request_operation_processing_queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + af_property_list_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.property-list-request.processing", DISPATCH_QUEUE_CONCURRENT); + }); return af_property_list_request_operation_processing_queue; } diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index c580840..42c74d4 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -59,6 +59,14 @@ The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (i.e. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this. Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as ["The Deallocation Problem"](http://developer.apple.com/library/ios/#technotes/tn2109/). + + ## SSL Pinning + + Relying on the CA trust model to validate SSL certificates exposes your app to security vulnerabilities, such as man-in-the-middle attacks. For applications that connect to known servers, SSL certificate pinning provides an increased level of security, by checking server certificate validity against those specified in the app bundle. + + SSL with certificate pinning is strongly recommended for any application that transmits sensitive information to an external webservice. + + When `_AFNETWORKING_PIN_SSL_CERTIFICATES_` is defined and the Security framework is linked, connections will be validated on all matching certificates with a `.cer` extension in the bundle root. ## NSCoding & NSCopying Conformance @@ -75,7 +83,21 @@ - A copy of an operation will not include the `outputStream` of the original. - Operation copies do not include `completionBlock`. `completionBlock` often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ operation when copied. */ -@interface AFURLConnectionOperation : NSOperation + +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ +typedef enum { + AFSSLPinningModeNone, + AFSSLPinningModePublicKey, + AFSSLPinningModeCertificate, +} AFURLConnectionOperationSSLPinningMode; +#endif + +@interface AFURLConnectionOperation : NSOperation = __IPHONE_5_0) || \ + (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8) +NSURLConnectionDataDelegate, +#endif +NSCoding, NSCopying> ///------------------------------- /// @name Accessing Run Loop Modes @@ -131,7 +153,6 @@ */ @property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding; - ///------------------------------- /// @name Managing URL Credentials ///------------------------------- @@ -150,6 +171,15 @@ */ @property (nonatomic, strong) NSURLCredential *credential; +/** + The pinning mode which will be used for SSL connections. `AFSSLPinningModePublicKey` by default. + + @discussion To enable SSL Pinning, `#define _AFNETWORKING_PIN_SSL_CERTIFICATES_` in `Prefix.pch`. Also, make sure that the Security framework is linked with the binary. See the "SSL Pinning" section in the `AFURLConnectionOperation`" header for more information. + */ +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ +@property (nonatomic, assign) AFURLConnectionOperationSSLPinningMode SSLPinningMode; +#endif + ///------------------------ /// @name Accessing Streams ///------------------------ @@ -290,6 +320,25 @@ ///---------------- /** + ## SSL Pinning Options + + The following constants are provided by `AFURLConnectionOperation` as possible SSL Pinning options. + + enum { + AFSSLPinningModeNone, + AFSSLPinningModePublicKey, + AFSSLPinningModeCertificate, + } + + `AFSSLPinningModeNone` + Do not pin SSL connections + + `AFSSLPinningModePublicKey` + Pin SSL connections to certificate public key (SPKI). + + `AFSSLPinningModeCertificate` + Pin SSL connections to exact certificate. This may cause problems when your certificate expires and needs re-issuance. + ## User info dictionary keys These keys may exist in the user info dictionary, in addition to those defined for NSError. diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 99e1693..ca9780c 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -145,6 +145,9 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @dynamic inputStream; @synthesize outputStream = _outputStream; @synthesize credential = _credential; +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ +@synthesize SSLPinningMode = _SSLPinningMode; +#endif @synthesize shouldUseCredentialStorage = _shouldUseCredentialStorage; @synthesize userInfo = _userInfo; @synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier; @@ -167,7 +170,6 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; - dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; @@ -176,24 +178,61 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat return _networkRequestThread; } +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ + (NSArray *)pinnedCertificates { static NSArray *_pinnedCertificates = nil; static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ NSBundle *bundle = [NSBundle bundleForClass:[self class]]; NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; - NSMutableArray *certificates = [NSMutableArray array]; + + NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]]; for (NSString *path in paths) { NSData *certificateData = [NSData dataWithContentsOfFile:path]; [certificates addObject:certificateData]; } + _pinnedCertificates = [[NSArray alloc] initWithArray:certificates]; }); return _pinnedCertificates; } ++ (NSArray *)pinnedPublicKeys { + static NSArray *_pinnedPublicKeys = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSArray *pinnedCertificates = [self pinnedCertificates]; + NSMutableArray *publicKeys = [NSMutableArray arrayWithCapacity:[pinnedCertificates count]]; + + for (NSData *data in pinnedCertificates) { + SecCertificateRef allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data); + NSCParameterAssert(allowedCertificate); + + SecCertificateRef allowedCertificates[] = {allowedCertificate}; + CFArrayRef certificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL); + + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecTrustRef allowedTrust = NULL; + OSStatus status = SecTrustCreateWithCertificates(certificates, policy, &allowedTrust); + NSAssert(status == noErr, @"SecTrustCreateWithCertificates error: %ld", (long int)status); + + SecKeyRef allowedPublicKey = SecTrustCopyPublicKey(allowedTrust); + [publicKeys addObject:(__bridge_transfer id)allowedPublicKey]; + + CFRelease(allowedTrust); + CFRelease(policy); + CFRelease(certificates); + CFRelease(allowedCertificate); + } + + _pinnedPublicKeys = [[NSArray alloc] initWithArray:publicKeys]; + }); + + return _pinnedPublicKeys; +} +#endif + - (id)initWithRequest:(NSURLRequest *)urlRequest { self = [super init]; if (!self) { @@ -344,19 +383,6 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat [self didChangeValueForKey:oldStateKey]; [self didChangeValueForKey:newStateKey]; [self.lock unlock]; - - dispatch_async(dispatch_get_main_queue(), ^{ - switch (state) { - case AFOperationExecutingState: - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; - break; - case AFOperationFinishedState: - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; - break; - default: - break; - } - }); } - (NSString *)responseString { @@ -371,7 +397,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat - (NSStringEncoding)responseStringEncoding { [self.lock lock]; - if (!_responseStringEncoding) { + if (!_responseStringEncoding && self.response) { NSStringEncoding stringEncoding = NSUTF8StringEncoding; if (self.response.textEncodingName) { CFStringEncoding IANAEncoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName); @@ -398,7 +424,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat [self.connection performSelector:@selector(cancel) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + [notificationCenter postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; }); } @@ -453,24 +480,34 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat - (void)operationDidStart { [self.lock lock]; - if ([self isCancelled]) { - [self finish]; - } else { + if (! [self isCancelled]) { self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; - + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; for (NSString *runLoopMode in self.runLoopModes) { [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; } - + [self.connection start]; } [self.lock unlock]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; + }); + + if ([self isCancelled]) { + [self finish]; + } } - (void)finish { self.state = AFOperationFinishedState; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + }); } - (void)cancel { @@ -517,12 +554,57 @@ willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challe NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { - [[challenge sender] cancelAuthenticationChallenge:challenge]; + switch (self.SSLPinningMode) { + case AFSSLPinningModePublicKey: { + id publicKey = (__bridge_transfer id)SecTrustCopyPublicKey(serverTrust); + + if ([[self.class pinnedPublicKeys] containsObject:publicKey]) { + NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] cancelAuthenticationChallenge:challenge]; + } + + break; + } + case AFSSLPinningModeCertificate: { + SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0); + NSData *serverCertificateData = (__bridge_transfer NSData *)SecCertificateCopyData(serverCertificate); + + if ([[[self class] pinnedCertificates] containsObject:serverCertificateData]) { + NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] cancelAuthenticationChallenge:challenge]; + } + + break; + } + case AFSSLPinningModeNone: { +#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_ + NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; +#else + SecTrustResultType result = 0; + OSStatus status = SecTrustEvaluate(serverTrust, &result); + NSAssert(status == noErr, @"SecTrustEvaluate error: %ld", (long int)status); + + if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) { + NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; + [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] cancelAuthenticationChallenge:challenge]; + } +#endif + break; + } + } } } } #endif + - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { @@ -631,18 +713,18 @@ didReceiveResponse:(NSURLResponse *)response - (void)connection:(NSURLConnection __unused *)connection didReceiveData:(NSData *)data { - self.totalBytesRead += [data length]; - if ([self.outputStream hasSpaceAvailable]) { const uint8_t *dataBuffer = (uint8_t *) [data bytes]; [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; } - - if (self.downloadProgress) { - dispatch_async(dispatch_get_main_queue(), ^{ + + dispatch_async(dispatch_get_main_queue(), ^{ + self.totalBytesRead += [data length]; + + if (self.downloadProgress) { self.downloadProgress([data length], self.totalBytesRead, self.response.expectedContentLength); - }); - } + } + }); } - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { diff --git a/AFNetworking/AFXMLRequestOperation.m b/AFNetworking/AFXMLRequestOperation.m index 7e6b352..30a76b9 100644 --- a/AFNetworking/AFXMLRequestOperation.m +++ b/AFNetworking/AFXMLRequestOperation.m @@ -24,11 +24,12 @@ #include -static dispatch_queue_t af_xml_request_operation_processing_queue; static dispatch_queue_t xml_request_operation_processing_queue() { - if (af_xml_request_operation_processing_queue == NULL) { - af_xml_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.xml-request.processing", 0); - } + static dispatch_queue_t af_xml_request_operation_processing_queue; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + af_xml_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.xml-request.processing", DISPATCH_QUEUE_CONCURRENT); + }); return af_xml_request_operation_processing_queue; } diff --git a/AFNetworking/UIImageView+AFNetworking.m b/AFNetworking/UIImageView+AFNetworking.m index 2e8b9c1..f340997 100644 --- a/AFNetworking/UIImageView+AFNetworking.m +++ b/AFNetworking/UIImageView+AFNetworking.m @@ -87,7 +87,6 @@ static char kAFImageRequestOperationObjectKey; placeholderImage:(UIImage *)placeholderImage { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - [request setHTTPShouldHandleCookies:NO]; [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil]; @@ -102,21 +101,22 @@ static char kAFImageRequestOperationObjectKey; UIImage *cachedImage = [[[self class] af_sharedImageCache] cachedImageForRequest:urlRequest]; if (cachedImage) { - self.image = cachedImage; - self.af_imageRequestOperation = nil; - if (success) { success(nil, nil, cachedImage); + } else { + self.image = cachedImage; } + + self.af_imageRequestOperation = nil; } else { self.image = placeholderImage; AFImageRequestOperation *requestOperation = [[AFImageRequestOperation alloc] initWithRequest:urlRequest]; [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { - if ([[urlRequest URL] isEqual:[[self.af_imageRequestOperation request] URL]]) { + if ([urlRequest isEqual:[self.af_imageRequestOperation request]]) { if (success) { success(operation.request, operation.response, responseObject); - } else { + } else if (responseObject) { self.image = responseObject; } @@ -127,7 +127,7 @@ static char kAFImageRequestOperationObjectKey; [[[self class] af_sharedImageCache] cacheImage:responseObject forRequest:urlRequest]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - if ([[urlRequest URL] isEqual:[[self.af_imageRequestOperation request] URL]]) { + if ([urlRequest isEqual:[self.af_imageRequestOperation request]]) { if (failure) { failure(operation.request, operation.response, error); } diff --git a/CHANGES b/CHANGES index 70f5a33..0767b58 100644 --- a/CHANGES +++ b/CHANGES @@ -1,67 +1,195 @@ += 1.2.0 (2013-03-24) + + * Add `SSLPinningMode` property to `AFHTTPClient` (Oliver Letterer, Kevin +Harwood, Adam Becevello, Dustin Barker, Mattt Thompson) + + * Add single quote ("'"), comma (","), and asterix ("*") to escaped URL +encoding characters (Eric Florenzano, Marc Nijdam, Garrett Murray) + + * Add `credential` property to `AFURLConnectionOperation` (Mattt Thompson) + + * Add `-setDefaultCredential:` to `AFHTTPClient` + + * Add `shouldUseCredentialStorage` property to `AFURLConnectionOperation` +(Mattt Thompson) + + * Add support for repeated key value pairs in `AFHTTPClient` URL query string +(Nick Dawson) + + * Add `AFMultipartFormData - +appendPartWithFileURL:name:fileName:mimeType:error` (Daniel Rodríguez Troitiño) + + * Add `AFMultipartFormData - +appendPartWithInputStream:name:fileName:mimeType:` (@joein3d) + + * Change SSL pinning to be runtime property on `AFURLConnectionOperation` +rather than defined by macro (Oliver Letterer) + + * Change `AFMultipartBodyStream` to `AFMultipartBodyStreamProvider`, vending +one side of a bound CFStream pair rather than subclassing `NSInputStream` (Mike +Ash) + + * Change default `Accept-Language` header in `AFHTTPClient` (@therigu, Mattt +Thompson) + + * Change `AFHTTPClient` operation cancellation to be based on request URL path +rather than absolute URL string (Mattt Thompson) + + * Change request operation subclass processing queues to use +`DISPATCH_QUEUE_CONCURRENT` (Mattt Thompson) + + * Change `UIImageView+AFNetworking` to resolve asymmetry in cached image case +between success block provided and not provided (@Eveets, Mattt Thompson) + + * Change `UIImageView+AFNetworking` to compare `NSURLRequest` instead of +`NSURL` to determine if previous request was equivalent (Cédric Luthi) + + * Change `UIImageView+AFNetworking` to only set image if non-`nil` (Sean +Kovacs) + + * Change indentation settings to four spaces at the project level (Cédric +Luthi) + + * Change `AFNetworkActivityIndicatorManager` to only update if requests have a +non-`nil` URL (Cédric Luthi) + + * Change `UIImageView+AFNetworking` to not do `setHTTPShouldHandleCookies` +(Konstantinos Vaggelakos) + + * Fix request stream exhaustion error on authentication challenges (Alex +Burgel) + + * Fix implementation to use `NSURL` methods instead of `CFURL` functions where +applicable (Cédric Luthi) + + * Fix race condition in `UIImageView+AFNetworking` (Peyman) + + * Fix `responseJSON`, `responseString`, and `responseStringEncoding` to be +threadsafe (Jon Parise, Mattt Thompson) + + * Fix `AFContentTypeForPathExtension` to ensure non-`NULL` content return +value (Zach Waugh) + + * Fix documentation for `appendPartWithFileURL:name:error:` + (Daniel Rodríguez Troitiño) + + * Fix request operation subclass processing queues to initialize with +`dispatch_once` (Sasmito Adibowo) + + * Fix posting of `AFNetworkingOperationDidStartNotification` and +`AFNetworkingOperationDidFinishNotification` to avoid crashes when logging in +response to notifications (Blake Watters) + + * Fix ordering of registered operation consultation in `AFHTTPClient` (Joel +Parsons) + + * Fix warning: multiple methods named 'postNotificationName:object:' found +[-Wstrict-selector-match] (Oliver Jones) + + * Fix warning: multiple methods named 'objectForKey:' found +[-Wstrict-selector-match] (Oliver Jones) + + * Fix warning: weak receiver may be unpredictably set to nil +[-Wreceiver-is-weak] (Oliver Jones) + + * Fix missing #pragma clang diagnostic pop (Steven Fisher) + = 1.1.0 (2012-12-27) - * Add optional SSL certificate pinning with `#define _AFNETWORKING_PIN_SSL_CERTIFICATES_` (Dustin Barker) + * Add optional SSL certificate pinning with `#define +_AFNETWORKING_PIN_SSL_CERTIFICATES_` (Dustin Barker) - * Add `responseStringEncoding` property to `AFURLConnectionOperation` (Mattt Thompson) + * Add `responseStringEncoding` property to `AFURLConnectionOperation` (Mattt +Thompson) - * Add `userInfo` property to `AFURLConnectionOperation` (Mattt Thompson, Steven Fisher) + * Add `userInfo` property to `AFURLConnectionOperation` (Mattt Thompson, +Steven Fisher) - * Change behavior to cause a failure when an operation is cancelled (Daniel Tull) + * Change behavior to cause a failure when an operation is cancelled (Daniel +Tull) * Change return type of class constructors to `instancetype` (@guykogus) - * Change notifications to always being posted on an asynchronously-dispatched block run on the main queue (Evadne Wu, Mattt Thompson) + * Change notifications to always being posted on an asynchronously-dispatched +block run on the main queue (Evadne Wu, Mattt Thompson) - * Change from NSLocalizedString to NSLocalizedStringFromTable with AFNetworking.strings table for localized strings (Cédric Luthi) + * Change from NSLocalizedString to NSLocalizedStringFromTable with +AFNetworking.strings table for localized strings (Cédric Luthi) - * Change `-appendPartWithHeaders:body:` to add assertion handler for existence of body data parameter (Jonathan Beilin) + * Change `-appendPartWithHeaders:body:` to add assertion handler for existence +of body data parameter (Jonathan Beilin) - * Change `AFHTTPRequestOperation -responseString` to follow guidelines from RFC 2616 regarding the use of string encoding when none is specified in the response (Jorge Bernal) + * Change `AFHTTPRequestOperation -responseString` to follow guidelines from +RFC 2616 regarding the use of string encoding when none is specified in the +response (Jorge Bernal) - * Change AFHTTPClient parameter serialization dictionary keys with `caseInsensitiveCompare:` to ensure - deterministic ordering of query string parameters, which may otherwise - cause ambiguous representations of nested parameters (James Coleman, + * Change AFHTTPClient parameter serialization dictionary keys with +`caseInsensitiveCompare:` to ensure + deterministic ordering of query string parameters, which may otherwise + cause ambiguous representations of nested parameters (James Coleman, Mattt Thompson) - * Fix -Wstrict-selector-match warnings raised by Xcode 4.6DP3 (Jesse Collis, Cédric Luthi) + * Fix -Wstrict-selector-match warnings raised by Xcode 4.6DP3 (Jesse Collis, +Cédric Luthi) - * Fix NSJSONSerialization crash with Unicode character escapes in JSON response (Mathijs Kadijk) + * Fix NSJSONSerialization crash with Unicode character escapes in JSON +response (Mathijs Kadijk) - * Fix issue with early return in -startMonitoringNetworkReachability if network reachability object could not be created (i.e. invalid hostnames) (Basil Shkara) + * Fix issue with early return in -startMonitoringNetworkReachability if +network reachability object could not be created (i.e. invalid hostnames) +(Basil Shkara) - * Fix retain cycles in AFImageRequestOperation.m and AFHTTPClient.m caused by strong references within blocks (Nick Forge) + * Fix retain cycles in AFImageRequestOperation.m and AFHTTPClient.m caused by +strong references within blocks (Nick Forge) - * Fix issue caused by Rails behavior of returning a single space in head :ok responses, which is interpreted as invalid (Sebastian Ludwig) + * Fix issue caused by Rails behavior of returning a single space in head :ok +responses, which is interpreted as invalid (Sebastian Ludwig) - * Fix issue in streaming multipart upload, where final encapsulation boundary would not be appended if it was larger than the available buffer, causing a potential timeout (Tomohisa Takaoka, David Kasper) + * Fix issue in streaming multipart upload, where final encapsulation boundary +would not be appended if it was larger than the available buffer, causing a +potential timeout (Tomohisa Takaoka, David Kasper) * Fix memory leak of network reachability callback block (Mattt Thompson) - * Fix `-initWithCoder:` for `AFURLConnectionOperation` and `AFHTTPClient` to cast scalar types (Mattt Thompson) + * Fix `-initWithCoder:` for `AFURLConnectionOperation` and `AFHTTPClient` to +cast scalar types (Mattt Thompson) - * Fix bug in `-enqueueBatchOfHTTPRequestOperations:...` to by using `addOperations:waitUntilFinished:` instead of adding each operation individually. (Mattt Thompson) + * Fix bug in `-enqueueBatchOfHTTPRequestOperations:...` to by using +`addOperations:waitUntilFinished:` instead of adding each operation +individually. (Mattt Thompson) - * Change `#warning` messages of checks for `CoreServices` and `MobileCoreServices` to message according to the build target platform (Mattt Thompson) + * Change `#warning` messages of checks for `CoreServices` and +`MobileCoreServices` to message according to the build target platform (Mattt +Thompson) - * Change `AFQueryStringFromParametersWithEncoding` to create keys string representations using the description method as specified in documentation (Cédric Luthi) + * Change `AFQueryStringFromParametersWithEncoding` to create keys string +representations using the description method as specified in documentation +(Cédric Luthi) * Fix __unused keywords for better Xcode indexing (Christian Rasmussen) * Fix warning: unused parameter 'x' [-Werror,-Wunused-parameter] (Oliver Jones) - - * Fix warning: property is assumed atomic by default [-Werror,-Wimplicit-atomic-properties] (Oliver Jones) - - * Fix warning: weak receiver may be unpredictably null in ARC mode [-Werror,-Wreceiver-is-weak] (Oliver Jones) - - * Fix warning: multiple methods named 'selector' found [-Werror,-Wstrict-selector-match] (Oliver Jones) - + + * Fix warning: property is assumed atomic by default +[-Werror,-Wimplicit-atomic-properties] (Oliver Jones) + + * Fix warning: weak receiver may be unpredictably null in ARC mode +[-Werror,-Wreceiver-is-weak] (Oliver Jones) + + * Fix warning: multiple methods named 'selector' found +[-Werror,-Wstrict-selector-match] (Oliver Jones) + * Fix warning: 'macro' is not defined, evaluates to 0 (Oliver Jones) - - * Fix warning: atomic by default property 'X' has a user (Oliver Jones)defined getter (property should be marked 'atomic' if this is intended) [-Werror, -Wcustom-atomic-properties] (Oliver Jones) - - * Fix warning: 'response' was marked unused but was used [-Werror,-Wused-but-marked-unused] (Oliver Jones) - - * Fix warning: enumeration value 'AFFinalBoundaryPhase' not explicitly handled in switch [-Werror,-Wswitch-enum] (Oliver Jones) + + * Fix warning: atomic by default property 'X' has a user (Oliver Jones)defined +getter (property should be marked 'atomic' if this is intended) [-Werror, +-Wcustom-atomic-properties] (Oliver Jones) + + * Fix warning: 'response' was marked unused but was used +[-Werror,-Wused-but-marked-unused] (Oliver Jones) + + * Fix warning: enumeration value 'AFFinalBoundaryPhase' not explicitly handled +in switch [-Werror,-Wswitch-enum] (Oliver Jones) = 1.0.1 / 2012-11-01 @@ -72,7 +200,7 @@ was not correctly calculated (Stan Chang Khin Boon) placeholderImage:success:failure`:, it is now the responsibility of the block to set the image of the image view (Mattt Thompson) - * Add `JSONReadingOptions` property to `AFJSONRequestOperation` (Jeremy + * Add `JSONReadingOptions` property to `AFJSONRequestOperation` (Jeremy Foo, Mattt Thompson) * Using __weak self / __strong self pattern to break retain cycles in @@ -80,10 +208,10 @@ block to set the image of the image view (Mattt Thompson) * Fix parameter encoding to leave period (`.`) unescaped (Diego Torres) - * Fixing last file component in multipart form part creation (Sylver - Bruneau) - - * Remove executable permission on AFHTTPClient source files (Andrew + * Fixing last file component in multipart form part creation (Sylver + Bruneau) + + * Remove executable permission on AFHTTPClient source files (Andrew Sardone) * Fix warning (error with -Werror) on implicit 64 to 32 conversion (Dan @@ -96,144 +224,144 @@ block to set the image of the image view (Mattt Thompson) = 1.0 / 2012-10-15 * AFNetworking now requires iOS 5 / Mac OSX 10.7 or higher (Mattt Thompson) - + * AFNetworking now uses Automatic Reference Counting (ARC) (Mattt Thompson) - * AFNetworking raises compiler warnings for missing features when -SystemConfiguration or CoreServices / MobileCoreServices frameworks are not -included in the project and imported in the precompiled headers (Mattt + * AFNetworking raises compiler warnings for missing features when +SystemConfiguration or CoreServices / MobileCoreServices frameworks are not +included in the project and imported in the precompiled headers (Mattt Thompson) - * AFNetworking now raises compiler error when not compiled with ARC (Steven + * AFNetworking now raises compiler error when not compiled with ARC (Steven Fisher) - * Add `NSCoding` and `NSCopying` protocol conformance to + * Add `NSCoding` and `NSCopying` protocol conformance to `AFURLConnectionOperation` and `AFHTTPClient` (Mattt Thompson) - * Add substantial improvements HTTP multipart streaming support, having -files streamed directly from disk and read sequentially from a custom input + * Add substantial improvements HTTP multipart streaming support, having +files streamed directly from disk and read sequentially from a custom input stream (Max Lansing, Stan Chang Khin Boon, Mattt Thompson) - - * Add `AFMultipartFormData -throttleBandwidthWithPacketSize:delay:` as + + * Add `AFMultipartFormData -throttleBandwidthWithPacketSize:delay:` as workaround to issues when uploading over 3G (Mattt Thompson) - * Add request and response to `userInfo` of errors returned from failing + * Add request and response to `userInfo` of errors returned from failing `AFHTTPRequestOperation` (Mattt Thompson) - * Add `userInfo` dictionary with current status in reachability changes + * Add `userInfo` dictionary with current status in reachability changes (Mattt Thompson) - * Add `Accept` header for image requests in `UIImageView` category (Bratley + * Add `Accept` header for image requests in `UIImageView` category (Bratley Lower) - * Add explicit declaration of `NSURLConnection` delegate methods so that + * Add explicit declaration of `NSURLConnection` delegate methods so that they can be overridden in subclasses (Mattt Thompson, Evan Grim) - * Add parameter validation to match conditions specified in documentation + * Add parameter validation to match conditions specified in documentation (Jason Brennan, Mattt Thompson) - * Add import to `UIKit` to avoid build errors from `UIDevice` references in + * Add import to `UIKit` to avoid build errors from `UIDevice` references in `User-Agent` default header (Blake Watters) * Remove `AFJSONUtilities` in favor of `NSJSONSerialization` (Mattt Thompson) - * Remove `extern` declaration of `AFURLEncodedStringFromStringWithEncoding` -function (`CFURLCreateStringByAddingPercentEscapes` should be used instead) + * Remove `extern` declaration of `AFURLEncodedStringFromStringWithEncoding` +function (`CFURLCreateStringByAddingPercentEscapes` should be used instead) (Mattt Thompson) - * Remove `setHTTPShouldHandleCookies:NO` from `AFHTTPClient` (@phamsonha, + * Remove `setHTTPShouldHandleCookies:NO` from `AFHTTPClient` (@phamsonha, Mattt Thompson) - - * Remove `dispatch_retain` / `dispatch_release` with ARC in iOS 6 (Benoit + + * Remove `dispatch_retain` / `dispatch_release` with ARC in iOS 6 (Benoit Bourdon) - + * Fix threading issue with `AFNetworkActivityIndicatorManager` (Eric Patey) - * Fix issue where `AFNetworkActivityIndicatorManager` count could become + * Fix issue where `AFNetworkActivityIndicatorManager` count could become negative (@ap4y) - * Fix properties to explicitly set options to suppress warnings (Wen-Hao + * Fix properties to explicitly set options to suppress warnings (Wen-Hao Lue, Mattt Thompson) - * Fix compiler warning caused by mismatched types in upload / download + * Fix compiler warning caused by mismatched types in upload / download progress blocks (Gareth du Plooy, tomas.a) - - * Fix weak / strong variable relationships in `completionBlock` (Peter + + * Fix weak / strong variable relationships in `completionBlock` (Peter Steinberger) - * Fix string formatting syntax warnings caused by type mismatch (David + * Fix string formatting syntax warnings caused by type mismatch (David Keegan, Steven Fisher, George Cox) - * Fix minor potential security vulnerability by explicitly using string + * Fix minor potential security vulnerability by explicitly using string format in NSError localizedDescription value in userInfo (Steven Fisher) - * Fix `AFURLConnectionOperation -pause` by adding state checks to prevent + * Fix `AFURLConnectionOperation -pause` by adding state checks to prevent likely memory issues when resuming (Mattt Thompson) - - * Fix warning caused by miscast of type when + + * Fix warning caused by miscast of type when `CLANG_WARN_IMPLICIT_SIGN_CONVERSION` is set (Steven Fisher) * Fix incomplete implementation warning in example code (Steven Fisher) * Fix warning caused by using `==` comparator on floats (Steven Fisher) - * Fix iOS 4 bug where file URLs return `NSURLResponse` rather than + * Fix iOS 4 bug where file URLs return `NSURLResponse` rather than `NSHTTPURLResponse` objects (Leo Lobato) - * Fix calculation of finished operations in batch operation progress + * Fix calculation of finished operations in batch operation progress callback (Mattt Thompson) - * Fix documentation typos (Steven Fisher, Matthias Wessendorf, + * Fix documentation typos (Steven Fisher, Matthias Wessendorf, jorge@miv.uk.com) - * Fix `hasAcceptableStatusCode` to return true after a network failure (Tony + * Fix `hasAcceptableStatusCode` to return true after a network failure (Tony Million) - * Fix warning about missing prototype for private static method (Stephan + * Fix warning about missing prototype for private static method (Stephan Diederich) - * Fix issue where `nil` content type resulted in unacceptable content type + * Fix issue where `nil` content type resulted in unacceptable content type (Mattt Thompson) * Fix bug related to setup and scheduling of output stream (Stephen Tramer) - * Fix AFContentTypesFromHTTPHeader to correctly handle comma-delimited + * Fix AFContentTypesFromHTTPHeader to correctly handle comma-delimited content types (Peyman, Mattt Thompson, @jsm174) - * Fix crash caused by `_networkReachability` not being set to `NULL` after + * Fix crash caused by `_networkReachability` not being set to `NULL` after releasing (Blake Watters) - * Fix Podspec to correctly import required headers and use ARC (Eloy Durán, + * Fix Podspec to correctly import required headers and use ARC (Eloy Durán, Blake Watters) - * Fix query string parameter escaping to leave square brackets unescaped + * Fix query string parameter escaping to leave square brackets unescaped (Mattt Thompson) * Fix query string parameter encoding of `NSNull` values (Daniel Rinser) - * Fix error caused by referencing `__IPHONE_OS_VERSION_MIN_REQUIRED` without + * Fix error caused by referencing `__IPHONE_OS_VERSION_MIN_REQUIRED` without importing `Availability.h` (Blake Watters) - * Update example to use App.net API, as Twitter shut off its unauthorized + * Update example to use App.net API, as Twitter shut off its unauthorized access to the public timeline (Mattt Thompson) - * Update `AFURLConnectionOperation` to replace `NSAutoReleasePool` with + * Update `AFURLConnectionOperation` to replace `NSAutoReleasePool` with `@autoreleasepool` (Mattt Thompson) - * Update `AFHTTPClient` operation queue to specify -`NSOperationQueueDefaultMaxConcurrentOperationCount` rather than + * Update `AFHTTPClient` operation queue to specify +`NSOperationQueueDefaultMaxConcurrentOperationCount` rather than previously-defined constant (Mattt Thompson) - * Update `AFHTTPClient -initWithBaseURL` to automatically append trailing -slash, so as to fix common issue where default path is not respected without + * Update `AFHTTPClient -initWithBaseURL` to automatically append trailing +slash, so as to fix common issue where default path is not respected without trailing slash (Steven Fisher) - - * Update default `AFHTTPClient` `User-Agent` header strings (Mattt Thompson, + + * Update default `AFHTTPClient` `User-Agent` header strings (Mattt Thompson, Steven Fisher) - + * Update icons for iOS example application (Mattt Thompson) - * Update `numberOfCompletedOperations` variable in progress block to be + * Update `numberOfCompletedOperations` variable in progress block to be renamed to `numberOfFinishedOperations` (Mattt Thompson) diff --git a/Example/AFNetworking Mac Example.xcodeproj/project.pbxproj b/Example/AFNetworking Mac Example.xcodeproj/project.pbxproj index 59617d1..52132e4 100644 --- a/Example/AFNetworking Mac Example.xcodeproj/project.pbxproj +++ b/Example/AFNetworking Mac Example.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ F82EB080159A172000B10B56 /* AFPropertyListRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F82EB077159A172000B10B56 /* AFPropertyListRequestOperation.m */; }; F82EB081159A172000B10B56 /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F82EB079159A172000B10B56 /* AFURLConnectionOperation.m */; }; F82EB082159A172000B10B56 /* AFXMLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F82EB07B159A172000B10B56 /* AFXMLRequestOperation.m */; }; + F88812F216C533E9003C8B8C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F88812F116C533E9003C8B8C /* Security.framework */; }; F8A847CF161F55A500940F39 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8A847CE161F55A500940F39 /* CoreServices.framework */; }; F8A847D2161F55AC00940F39 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8A847D1161F55AC00940F39 /* SystemConfiguration.framework */; }; /* End PBXBuildFile section */ @@ -57,6 +58,7 @@ F82EB07A159A172000B10B56 /* AFXMLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFXMLRequestOperation.h; path = ../AFNetworking/AFXMLRequestOperation.h; sourceTree = ""; }; F82EB07B159A172000B10B56 /* AFXMLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFXMLRequestOperation.m; path = ../AFNetworking/AFXMLRequestOperation.m; sourceTree = ""; }; F877018B159A1CE700B45C0D /* AFNetworking Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "AFNetworking Example.entitlements"; sourceTree = ""; }; + F88812F116C533E9003C8B8C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; F8A847CE161F55A500940F39 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; F8A847D1161F55AC00940F39 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -66,6 +68,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F88812F216C533E9003C8B8C /* Security.framework in Frameworks */, F8A847D2161F55AC00940F39 /* SystemConfiguration.framework in Frameworks */, F8A847CF161F55A500940F39 /* CoreServices.framework in Frameworks */, F8129C001591061B009BFE23 /* Cocoa.framework in Frameworks */, @@ -78,6 +81,7 @@ F8129BF01591061B009BFE23 = { isa = PBXGroup; children = ( + F88812F116C533E9003C8B8C /* Security.framework */, F8A847D1161F55AC00940F39 /* SystemConfiguration.framework */, F8A847CE161F55A500940F39 /* CoreServices.framework */, F877018B159A1CE700B45C0D /* AFNetworking Example.entitlements */, @@ -86,7 +90,10 @@ F8129BFE1591061B009BFE23 /* Frameworks */, F8129BFC1591061B009BFE23 /* Products */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; + usesTabs = 0; }; F8129BFC1591061B009BFE23 /* Products */ = { isa = PBXGroup; @@ -206,7 +213,7 @@ F8129BF21591061B009BFE23 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0450; + LastUpgradeCheck = 0460; }; buildConfigurationList = F8129BF51591061B009BFE23 /* Build configuration list for PBXProject "AFNetworking Mac Example" */; compatibilityVersion = "Xcode 3.2"; @@ -265,6 +272,10 @@ ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; @@ -292,6 +303,10 @@ ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = "$(ARCHS_STANDARD_64_BIT)"; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu99; diff --git a/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj b/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj index 16afe14..d89b181 100644 --- a/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj +++ b/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ F8129C7415910C37009BFE23 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F8129C7215910C37009BFE23 /* AppDelegate.m */; }; F818101615E6A0C600EF93C2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ABD6EC159FC2CE001BE42C /* MobileCoreServices.framework */; }; + F88812F016C533D6003C8B8C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E013957DF100DB05C8 /* Security.framework */; }; F8A847C1161F51A300940F39 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F8A847C0161F51A300940F39 /* Default-568h@2x.png */; }; F8A847C3161F523E00940F39 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = F8A847C2161F523E00940F39 /* Default.png */; }; F8A847C5161F524200940F39 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F8A847C4161F524200940F39 /* Default@2x.png */; }; @@ -95,6 +96,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F88812F016C533D6003C8B8C /* Security.framework in Frameworks */, F8E469651395739D00DB05C8 /* UIKit.framework in Frameworks */, F8E469671395739D00DB05C8 /* Foundation.framework in Frameworks */, F8E469691395739D00DB05C8 /* CoreGraphics.framework in Frameworks */, @@ -152,7 +154,10 @@ F8E469631395739D00DB05C8 /* Frameworks */, F8E469611395739C00DB05C8 /* Products */, ); + indentWidth = 4; sourceTree = ""; + tabWidth = 4; + usesTabs = 0; }; F8E469611395739C00DB05C8 /* Products */ = { isa = PBXGroup; diff --git a/Example/Classes/Models/User.h b/Example/Classes/Models/User.h index 99d973b..20cd5de 100644 --- a/Example/Classes/Models/User.h +++ b/Example/Classes/Models/User.h @@ -26,9 +26,9 @@ extern NSString * const kUserProfileImageDidLoadNotification; @interface User : NSObject -@property (readonly) NSUInteger userID; -@property (readonly) NSString *username; -@property (unsafe_unretained, readonly) NSURL *avatarImageURL; +@property (readonly, nonatomic) NSUInteger userID; +@property (readonly, nonatomic) NSString *username; +@property (readonly, nonatomic, unsafe_unretained) NSURL *avatarImageURL; - (id)initWithAttributes:(NSDictionary *)attributes; diff --git a/Example/Prefix.pch b/Example/Prefix.pch index 18b984d..df78b3c 100644 --- a/Example/Prefix.pch +++ b/Example/Prefix.pch @@ -1,5 +1,7 @@ #import +#define _AFNETWORKING_PIN_SSL_CERTIFICATES_ + #if __IPHONE_OS_VERSION_MIN_REQUIRED #ifndef __IPHONE_3_0 #warning "This project uses features only available in iPhone SDK 3.0 and later."