2012-07-18 12:17:55 -07:00
// AFHTTPRequestOperation . m
//
// Copyright ( c ) 2011 Gowalla ( http : // gowalla . com / )
2013-09-25 17:43:00 -07:00
//
2012-07-18 12:17:55 -07:00
// Permission is hereby granted , free of charge , to any person obtaining a copy
// of this software and associated documentation files ( the "Software" ) , to deal
// in the Software without restriction , including without limitation the rights
// to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
// copies of the Software , and to permit persons to whom the Software is
// furnished to do so , subject to the following conditions :
2013-09-25 17:43:00 -07:00
//
2012-07-18 12:17:55 -07:00
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software .
2013-09-25 17:43:00 -07:00
//
2012-07-18 12:17:55 -07:00
// THE SOFTWARE IS PROVIDED "AS IS" , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
// IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
// LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE .
# import "AFHTTPRequestOperation.h"
# import < objc / runtime . h >
2013-09-25 17:43:00 -07:00
// Workaround for change in imp_implementationWithBlock ( ) with Xcode 4.5
# if defined ( __IPHONE _6 _0 ) || defined ( __MAC _10 _8 )
# define AF_CAST _TO _BLOCK id
# else
# define AF_CAST _TO _BLOCK __bridge void *
# endif
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wstrict-selector-match"
2012-07-18 12:17:55 -07:00
NSSet * AFContentTypesFromHTTPHeader ( NSString * string ) {
if ( ! string ) {
return nil ;
}
2013-09-25 17:43:00 -07:00
NSArray * mediaRanges = [ string componentsSeparatedByString : @ "," ] ;
NSMutableSet * mutableContentTypes = [ NSMutableSet setWithCapacity : mediaRanges . count ] ;
[ mediaRanges enumerateObjectsUsingBlock : ^ ( NSString * mediaRange , __unused NSUInteger idx , __unused BOOL * stop ) {
NSRange parametersRange = [ mediaRange rangeOfString : @ ";" ] ;
if ( parametersRange . location ! = NSNotFound ) {
mediaRange = [ mediaRange substringToIndex : parametersRange . location ] ;
2012-07-18 12:17:55 -07:00
}
2013-09-25 17:43:00 -07:00
mediaRange = [ mediaRange stringByTrimmingCharactersInSet : [ NSCharacterSet whitespaceAndNewlineCharacterSet ] ] ;
if ( mediaRange . length > 0 ) {
[ mutableContentTypes addObject : mediaRange ] ;
2012-07-18 12:17:55 -07:00
}
2013-09-25 17:43:00 -07:00
} ] ;
2012-07-18 12:17:55 -07:00
return [ NSSet setWithSet : mutableContentTypes ] ;
}
2013-09-25 17:43:00 -07:00
static void AFGetMediaTypeAndSubtypeWithString ( NSString * string , NSString * * type , NSString * * subtype ) {
if ( ! string ) {
return ;
2012-07-18 12:17:55 -07:00
}
2013-09-25 17:43:00 -07:00
NSScanner * scanner = [ NSScanner scannerWithString : string ] ;
[ scanner setCharactersToBeSkipped : [ NSCharacterSet whitespaceAndNewlineCharacterSet ] ] ;
[ scanner scanUpToString : @ "/" intoString : type ] ;
[ scanner scanString : @ "/" intoString : nil ] ;
[ scanner scanUpToString : @ ";" intoString : subtype ] ;
2012-07-18 12:17:55 -07:00
}
static NSString * AFStringFromIndexSet ( NSIndexSet * indexSet ) {
NSMutableString * string = [ NSMutableString string ] ;
NSRange range = NSMakeRange ( [ indexSet firstIndex ] , 1 ) ;
while ( range . location ! = NSNotFound ) {
NSUInteger nextIndex = [ indexSet indexGreaterThanIndex : range . location ] ;
while ( nextIndex = = range . location + range . length ) {
range . length + + ;
nextIndex = [ indexSet indexGreaterThanIndex : nextIndex ] ;
}
if ( string . length ) {
[ string appendString : @ "," ] ;
}
if ( range . length = = 1 ) {
2013-09-25 17:43:00 -07:00
[ string appendFormat : @ "%lu" , ( long ) range . location ] ;
2012-07-18 12:17:55 -07:00
} else {
NSUInteger firstIndex = range . location ;
NSUInteger lastIndex = firstIndex + range . length - 1 ;
2013-09-25 17:43:00 -07:00
[ string appendFormat : @ "%lu-%lu" , ( long ) firstIndex , ( long ) lastIndex ] ;
2012-07-18 12:17:55 -07:00
}
range . location = nextIndex ;
range . length = 1 ;
}
return string ;
}
2013-09-25 17:43:00 -07:00
static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock ( Class klass , SEL selector , id block ) {
Method originalMethod = class_getClassMethod ( klass , selector ) ;
IMP implementation = imp_implementationWithBlock ( ( AF_CAST _TO _BLOCK ) block ) ;
class_replaceMethod ( objc_getMetaClass ( [ NSStringFromClass ( klass ) UTF8String ] ) , selector , implementation , method_getTypeEncoding ( originalMethod ) ) ;
2012-07-18 12:17:55 -07:00
}
# pragma mark -
@ interface AFHTTPRequestOperation ( )
2013-09-25 17:43:00 -07:00
@ property ( readwrite , nonatomic , strong ) NSURLRequest * request ;
@ property ( readwrite , nonatomic , strong ) NSHTTPURLResponse * response ;
@ property ( readwrite , nonatomic , strong ) NSError * HTTPError ;
2012-07-18 12:17:55 -07:00
@ end
@ implementation AFHTTPRequestOperation
@ synthesize HTTPError = _HTTPError ;
@ synthesize successCallbackQueue = _successCallbackQueue ;
@ synthesize failureCallbackQueue = _failureCallbackQueue ;
@ dynamic request ;
@ dynamic response ;
- ( void ) dealloc {
2013-09-25 17:43:00 -07:00
if ( _successCallbackQueue ) {
# if ! OS_OBJECT _USE _OBJC
2012-07-18 12:17:55 -07:00
dispatch_release ( _successCallbackQueue ) ;
2013-09-25 17:43:00 -07:00
# endif
2012-07-18 12:17:55 -07:00
_successCallbackQueue = NULL ;
}
2013-09-25 17:43:00 -07:00
if ( _failureCallbackQueue ) {
# if ! OS_OBJECT _USE _OBJC
dispatch_release ( _failureCallbackQueue ) ;
# endif
2012-07-18 12:17:55 -07:00
_failureCallbackQueue = NULL ;
}
}
- ( NSError * ) error {
2013-09-25 17:43:00 -07:00
if ( ! self . HTTPError && self . response ) {
if ( ! [ self hasAcceptableStatusCode ] || ! [ self hasAcceptableContentType ] ) {
2012-07-18 12:17:55 -07:00
NSMutableDictionary * userInfo = [ NSMutableDictionary dictionary ] ;
2013-09-25 17:43:00 -07:00
[ userInfo setValue : self . responseString forKey : NSLocalizedRecoverySuggestionErrorKey ] ;
2012-07-18 12:17:55 -07:00
[ userInfo setValue : [ self . request URL ] forKey : NSURLErrorFailingURLErrorKey ] ;
2013-09-25 17:43:00 -07:00
[ userInfo setValue : self . request forKey : AFNetworkingOperationFailingURLRequestErrorKey ] ;
[ userInfo setValue : self . response forKey : AFNetworkingOperationFailingURLResponseErrorKey ] ;
if ( ! [ self hasAcceptableStatusCode ] ) {
NSUInteger statusCode = ( [ self . response isKindOfClass : [ NSHTTPURLResponse class ] ] ) ? ( NSUInteger ) [ self . response statusCode ] : 200 ;
[ userInfo setValue : [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "Expected status code in (%@), got %d" , @ "AFNetworking" , nil ) , AFStringFromIndexSet ( [ [ self class ] acceptableStatusCodes ] ) , statusCode ] forKey : NSLocalizedDescriptionKey ] ;
self . HTTPError = [ [ NSError alloc ] initWithDomain : AFNetworkingErrorDomain code : NSURLErrorBadServerResponse userInfo : userInfo ] ;
} else if ( ! [ self hasAcceptableContentType ] ) {
// Don ' t invalidate content type if there is no content
if ( [ self . responseData length ] > 0 ) {
[ userInfo setValue : [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "Expected content type %@, got %@" , @ "AFNetworking" , nil ) , [ [ self class ] acceptableContentTypes ] , [ self . response MIMEType ] ] forKey : NSLocalizedDescriptionKey ] ;
self . HTTPError = [ [ NSError alloc ] initWithDomain : AFNetworkingErrorDomain code : NSURLErrorCannotDecodeContentData userInfo : userInfo ] ;
}
}
2012-07-18 12:17:55 -07:00
}
}
2013-09-25 17:43:00 -07:00
2012-07-18 12:17:55 -07:00
if ( self . HTTPError ) {
return self . HTTPError ;
} else {
return [ super error ] ;
}
}
2013-09-25 17:43:00 -07:00
- ( 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 . response && ! self . response . textEncodingName && self . responseData && [ self . response respondsToSelector : @ selector ( allHeaderFields ) ] ) {
NSString * type = nil ;
AFGetMediaTypeAndSubtypeWithString ( [ [ self . response allHeaderFields ] valueForKey : @ "Content-Type" ] , & type , nil ) ;
if ( [ type isEqualToString : @ "text" ] ) {
return NSISOLatin1StringEncoding ;
}
}
return [ super responseStringEncoding ] ;
}
2012-07-18 12:17:55 -07:00
- ( void ) pause {
2013-09-25 17:43:00 -07:00
unsigned long long offset = 0 ;
2012-07-18 12:17:55 -07:00
if ( [ self . outputStream propertyForKey : NSStreamFileCurrentOffsetKey ] ) {
offset = [ [ self . outputStream propertyForKey : NSStreamFileCurrentOffsetKey ] unsignedLongLongValue ] ;
} else {
offset = [ [ self . outputStream propertyForKey : NSStreamDataWrittenToMemoryStreamKey ] length ] ;
}
2013-09-25 17:43:00 -07:00
NSMutableURLRequest * mutableURLRequest = [ self . request mutableCopy ] ;
if ( [ self . response respondsToSelector : @ selector ( allHeaderFields ) ] && [ [ self . response allHeaderFields ] valueForKey : @ "ETag" ] ) {
2012-07-18 12:17:55 -07:00
[ mutableURLRequest setValue : [ [ self . response allHeaderFields ] valueForKey : @ "ETag" ] forHTTPHeaderField : @ "If-Range" ] ;
}
[ mutableURLRequest setValue : [ NSString stringWithFormat : @ "bytes=%llu-" , offset ] forHTTPHeaderField : @ "Range" ] ;
self . request = mutableURLRequest ;
2013-09-25 17:43:00 -07:00
2012-07-18 12:17:55 -07:00
[ super pause ] ;
}
- ( BOOL ) hasAcceptableStatusCode {
2013-09-25 17:43:00 -07:00
if ( ! self . response ) {
return NO ;
}
NSUInteger statusCode = ( [ self . response isKindOfClass : [ NSHTTPURLResponse class ] ] ) ? ( NSUInteger ) [ self . response statusCode ] : 200 ;
return ! [ [ self class ] acceptableStatusCodes ] || [ [ [ self class ] acceptableStatusCodes ] containsIndex : statusCode ] ;
2012-07-18 12:17:55 -07:00
}
- ( BOOL ) hasAcceptableContentType {
2013-09-25 17:43:00 -07:00
if ( ! self . response ) {
return NO ;
}
// Any HTTP / 1.1 message containing an entity - body SHOULD include a Content - Type header field defining the media type of that body . If and only if the media type is not given by a Content - Type field , the recipient MAY attempt to guess the media type via inspection of its content and / or the name extension ( s ) of the URI used to identify the resource . If the media type remains unknown , the recipient SHOULD treat it as type "application/octet-stream" .
// See http : // www . w3 . org / Protocols / rfc2616 / rfc2616 - sec7 . html
NSString * contentType = [ self . response MIMEType ] ;
if ( ! contentType ) {
contentType = @ "application/octet-stream" ;
}
return ! [ [ self class ] acceptableContentTypes ] || [ [ [ self class ] acceptableContentTypes ] containsObject : contentType ] ;
2012-07-18 12:17:55 -07:00
}
- ( void ) setSuccessCallbackQueue : ( dispatch_queue _t ) successCallbackQueue {
if ( successCallbackQueue ! = _successCallbackQueue ) {
if ( _successCallbackQueue ) {
2013-09-25 17:43:00 -07:00
# if ! OS_OBJECT _USE _OBJC
2012-07-18 12:17:55 -07:00
dispatch_release ( _successCallbackQueue ) ;
2013-09-25 17:43:00 -07:00
# endif
2012-07-18 12:17:55 -07:00
_successCallbackQueue = NULL ;
}
if ( successCallbackQueue ) {
2013-09-25 17:43:00 -07:00
# if ! OS_OBJECT _USE _OBJC
2012-07-18 12:17:55 -07:00
dispatch_retain ( successCallbackQueue ) ;
2013-09-25 17:43:00 -07:00
# endif
2012-07-18 12:17:55 -07:00
_successCallbackQueue = successCallbackQueue ;
}
2013-09-25 17:43:00 -07:00
}
2012-07-18 12:17:55 -07:00
}
- ( void ) setFailureCallbackQueue : ( dispatch_queue _t ) failureCallbackQueue {
if ( failureCallbackQueue ! = _failureCallbackQueue ) {
if ( _failureCallbackQueue ) {
2013-09-25 17:43:00 -07:00
# if ! OS_OBJECT _USE _OBJC
2012-07-18 12:17:55 -07:00
dispatch_release ( _failureCallbackQueue ) ;
2013-09-25 17:43:00 -07:00
# endif
2012-07-18 12:17:55 -07:00
_failureCallbackQueue = NULL ;
}
2013-09-25 17:43:00 -07:00
2012-07-18 12:17:55 -07:00
if ( failureCallbackQueue ) {
2013-09-25 17:43:00 -07:00
# if ! OS_OBJECT _USE _OBJC
2012-07-18 12:17:55 -07:00
dispatch_retain ( failureCallbackQueue ) ;
2013-09-25 17:43:00 -07:00
# endif
2012-07-18 12:17:55 -07:00
_failureCallbackQueue = failureCallbackQueue ;
}
2013-09-25 17:43:00 -07:00
}
2012-07-18 12:17:55 -07:00
}
- ( void ) setCompletionBlockWithSuccess : ( void ( ^ ) ( AFHTTPRequestOperation * operation , id responseObject ) ) success
failure : ( void ( ^ ) ( AFHTTPRequestOperation * operation , NSError * error ) ) failure
{
2013-09-25 17:43:00 -07:00
// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle .
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Warc-retain-cycles"
# pragma clang diagnostic ignored "-Wgnu"
self . completionBlock = ^ {
2012-07-18 12:17:55 -07:00
if ( self . error ) {
if ( failure ) {
2013-09-25 17:43:00 -07:00
dispatch_async ( self . failureCallbackQueue ? : dispatch_get _main _queue ( ) , ^ {
2012-07-18 12:17:55 -07:00
failure ( self , self . error ) ;
} ) ;
}
} else {
if ( success ) {
2013-09-25 17:43:00 -07:00
dispatch_async ( self . successCallbackQueue ? : dispatch_get _main _queue ( ) , ^ {
2012-07-18 12:17:55 -07:00
success ( self , self . responseData ) ;
} ) ;
}
}
} ;
2013-09-25 17:43:00 -07:00
# pragma clang diagnostic pop
2012-07-18 12:17:55 -07:00
}
# pragma mark - AFHTTPRequestOperation
+ ( NSIndexSet * ) acceptableStatusCodes {
return [ NSIndexSet indexSetWithIndexesInRange : NSMakeRange ( 200 , 100 ) ] ;
}
+ ( void ) addAcceptableStatusCodes : ( NSIndexSet * ) statusCodes {
2013-09-25 17:43:00 -07:00
NSMutableIndexSet * mutableStatusCodes = [ [ NSMutableIndexSet alloc ] initWithIndexSet : [ self acceptableStatusCodes ] ] ;
2012-07-18 12:17:55 -07:00
[ mutableStatusCodes addIndexes : statusCodes ] ;
2013-09-25 17:43:00 -07:00
AFSwizzleClassMethodWithClassAndSelectorUsingBlock ( [ self class ] , @ selector ( acceptableStatusCodes ) , ^ ( __unused id _self ) {
return mutableStatusCodes ;
} ) ;
2012-07-18 12:17:55 -07:00
}
+ ( NSSet * ) acceptableContentTypes {
return nil ;
}
+ ( void ) addAcceptableContentTypes : ( NSSet * ) contentTypes {
2013-09-25 17:43:00 -07:00
NSMutableSet * mutableContentTypes = [ [ NSMutableSet alloc ] initWithSet : [ self acceptableContentTypes ] copyItems : YES ] ;
2012-07-18 12:17:55 -07:00
[ mutableContentTypes unionSet : contentTypes ] ;
2013-09-25 17:43:00 -07:00
AFSwizzleClassMethodWithClassAndSelectorUsingBlock ( [ self class ] , @ selector ( acceptableContentTypes ) , ^ ( __unused id _self ) {
return mutableContentTypes ;
} ) ;
2012-07-18 12:17:55 -07:00
}
+ ( BOOL ) canProcessRequest : ( NSURLRequest * ) request {
if ( [ [ self class ] isEqual : [ AFHTTPRequestOperation class ] ] ) {
return YES ;
}
2013-09-25 17:43:00 -07:00
return [ [ self acceptableContentTypes ] intersectsSet : AFContentTypesFromHTTPHeader ( [ request valueForHTTPHeaderField : @ "Accept" ] ) ] ;
2012-07-18 12:17:55 -07:00
}
@ end
2013-09-25 17:43:00 -07:00
# pragma clang diagnostic pop