diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 7858029..2288c3a 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -43,6 +43,7 @@ - `connection:didFailWithError:` - `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:` - `connection:willCacheResponse:` + - `connection:willSendRequestForAuthenticationChallenge:` - `connection:canAuthenticateAgainstProtectionSpace:` - `connection:didReceiveAuthenticationChallenge:` - `connectionShouldUseCredentialStorage:` @@ -282,6 +283,18 @@ NSCoding, NSCopying> /// @name Setting NSURLConnection Delegate Callbacks ///------------------------------------------------- +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ +/** + Sets a block to be executed when the connection will authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequestForAuthenticationChallenge:`. + + @param block A block object to be executed when the connection will authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated. This block must invoke one of the challenge-responder methods (NSURLAuthenticationChallengeSender protocol). + + If `allowsInvalidSSLCertificate` is set to YES, `connection:willSendRequestForAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates. + */ +- (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block; + +#else + /** Sets a block to be executed to determine whether the connection should be able to respond to a protection space's form of authentication, as handled by the `NSURLConnectionDelegate` method `connection:canAuthenticateAgainstProtectionSpace:`. @@ -300,6 +313,8 @@ NSCoding, NSCopying> */ - (void)setAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block; +#endif + /** Sets a block to be executed when the server redirects the request from one URL to another URL, or when the request URL changed by the `NSURLProtocol` subclass handling the request in order to standardize its format, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequest:redirectResponse:`. diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index d173319..0f03915 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -56,7 +56,9 @@ NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.net NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; typedef void (^AFURLConnectionOperationProgressBlock)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); +#ifndef _AFNETWORKING_PIN_SSL_CERTIFICATES_ typedef BOOL (^AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace); +#endif typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); typedef NSURLRequest * (^AFURLConnectionOperationRedirectResponseBlock)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse); @@ -120,7 +122,9 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @property (readwrite, nonatomic, assign) AFBackgroundTaskIdentifier backgroundTaskIdentifier; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; +#ifndef _AFNETWORKING_PIN_SSL_CERTIFICATES_ @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock authenticationAgainstProtectionSpace; +#endif @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge; @property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse; @property (readwrite, nonatomic, copy) AFURLConnectionOperationRedirectResponseBlock redirectResponse; @@ -154,8 +158,10 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier; @synthesize uploadProgress = _uploadProgress; @synthesize downloadProgress = _downloadProgress; -@synthesize authenticationAgainstProtectionSpace = _authenticationAgainstProtectionSpace; @synthesize authenticationChallenge = _authenticationChallenge; +#ifndef _AFNETWORKING_PIN_SSL_CERTIFICATES_ +@synthesize authenticationAgainstProtectionSpace = _authenticationAgainstProtectionSpace; +#endif @synthesize cacheResponse = _cacheResponse; @synthesize redirectResponse = _redirectResponse; @synthesize lock = _lock; @@ -365,6 +371,14 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat self.downloadProgress = block; } +#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ + +- (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block { + self.authenticationChallenge = block; +} + +#else + - (void)setAuthenticationAgainstProtectionSpaceBlock:(BOOL (^)(NSURLConnection *, NSURLProtectionSpace *))block { self.authenticationAgainstProtectionSpace = block; } @@ -373,6 +387,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat self.authenticationChallenge = block; } +#endif + - (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block { self.cacheResponse = block; } @@ -555,9 +571,15 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat #pragma mark - NSURLConnectionDelegate #ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_ + - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + if (self.authenticationChallenge) { + self.authenticationChallenge(connection, challenge); + return; + } + if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; @@ -636,12 +658,20 @@ willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challe break; } } - } else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodDefault]) { - [[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge]; + } else { + if ([challenge previousFailureCount] == 0) { + if (self.credential) { + [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } + } else { + [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; + } } } -#endif +#else - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace @@ -685,6 +715,8 @@ didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge } } +#endif + - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection { return self.shouldUseCredentialStorage; } @@ -837,7 +869,9 @@ didReceiveResponse:(NSURLResponse *)response operation.uploadProgress = self.uploadProgress; operation.downloadProgress = self.downloadProgress; +#ifndef _AFNETWORKING_PIN_SSL_CERTIFICATES_ operation.authenticationAgainstProtectionSpace = self.authenticationAgainstProtectionSpace; +#endif operation.authenticationChallenge = self.authenticationChallenge; operation.cacheResponse = self.cacheResponse; operation.redirectResponse = self.redirectResponse; diff --git a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj index 1324a62..716e8e9 100644 --- a/Tests/AFNetworking Tests.xcodeproj/project.pbxproj +++ b/Tests/AFNetworking Tests.xcodeproj/project.pbxproj @@ -25,6 +25,10 @@ 25C4EC42173D86B60083E116 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 25C4EC30173D7DCA0083E116 /* MobileCoreServices.framework */; }; 29A9CE2117456336002360C8 /* AFJSONRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */; }; 29A9CE2217456336002360C8 /* AFJSONRequestOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */; }; + A7DC62A617592E4200EBEC2F /* AFTestURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */; }; + A7DC62A717592E4200EBEC2F /* AFTestURLProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */; }; + A7DC62A917592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */; }; + A7DC62AA17592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */; }; AC11A74923B64A3096ACADFC /* libPods-osx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 96A923755B00464187DEDBAF /* libPods-osx.a */; }; F8C6F282174D2C6200B154D5 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = F8C6F281174D2C6200B154D5 /* Icon.png */; }; F8C6F283174D2C6200B154D5 /* Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = F8C6F281174D2C6200B154D5 /* Icon.png */; }; @@ -74,6 +78,9 @@ 2B6D24F8E1B74E10A269E8B3 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 55E73C267F33406A9F92476C /* libPods-ios.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ios.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 96A923755B00464187DEDBAF /* libPods-osx.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-osx.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + A7DC62A417592E4200EBEC2F /* AFTestURLProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFTestURLProtocol.h; sourceTree = ""; }; + A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFTestURLProtocol.m; sourceTree = ""; }; + A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFURLConnectionOperationTests.m; sourceTree = ""; }; F8C6F281174D2C6200B154D5 /* Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Icon.png; path = ../Example/Icon.png; sourceTree = ""; }; /* End PBXFileReference section */ @@ -188,11 +195,14 @@ 25801548173EB3B00026AA6E /* Tests */ = { isa = PBXGroup; children = ( + A7DC62A417592E4200EBEC2F /* AFTestURLProtocol.h */, + A7DC62A517592E4200EBEC2F /* AFTestURLProtocol.m */, 2580153E173EB3A70026AA6E /* AFNetworkingTests.h */, 2580153F173EB3A70026AA6E /* AFNetworkingTests.m */, 2580153B173EB3A70026AA6E /* AFHTTPRequestOperationTests.m */, 29A9CE2017456336002360C8 /* AFJSONRequestOperationTests.m */, 2580153A173EB3A70026AA6E /* AFHTTPClientTests.m */, + A7DC62A817592E4800EBEC2F /* AFURLConnectionOperationTests.m */, ); name = Tests; sourceTree = ""; @@ -328,6 +338,8 @@ 25801542173EB3A70026AA6E /* AFHTTPRequestOperationTests.m in Sources */, 25801546173EB3A70026AA6E /* AFNetworkingTests.m in Sources */, 29A9CE2117456336002360C8 /* AFJSONRequestOperationTests.m in Sources */, + A7DC62A617592E4200EBEC2F /* AFTestURLProtocol.m in Sources */, + A7DC62A917592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -339,6 +351,8 @@ 25801543173EB3A70026AA6E /* AFHTTPRequestOperationTests.m in Sources */, 25801547173EB3A70026AA6E /* AFNetworkingTests.m in Sources */, 29A9CE2217456336002360C8 /* AFJSONRequestOperationTests.m in Sources */, + A7DC62A717592E4200EBEC2F /* AFTestURLProtocol.m in Sources */, + A7DC62AA17592E4800EBEC2F /* AFURLConnectionOperationTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/AFNetworking-Prefix.pch b/Tests/AFNetworking-Prefix.pch index 59f8ee7..6f34220 100644 --- a/Tests/AFNetworking-Prefix.pch +++ b/Tests/AFNetworking-Prefix.pch @@ -2,6 +2,8 @@ // Prefix header for all source files of the 'AFNetworking' target in the 'AFNetworking' project // +#define _AFNETWORKING_PIN_SSL_CERTIFICATES_ + #ifdef __OBJC__ #import diff --git a/Tests/AFTestURLProtocol.h b/Tests/AFTestURLProtocol.h new file mode 100644 index 0000000..6ede840 --- /dev/null +++ b/Tests/AFTestURLProtocol.h @@ -0,0 +1,34 @@ +// AFTestURLProtocol.h +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// 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: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// 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 + + + +/** + @abstract <#abstract comment#> + */ +@interface AFTestURLProtocol : NSURLProtocol + ++ (void)matchURL:(NSURL *)URL withCallback:(id(^)(AFTestURLProtocol *protocol))initializationCallback; + +@end diff --git a/Tests/AFTestURLProtocol.m b/Tests/AFTestURLProtocol.m new file mode 100644 index 0000000..95d5e4d --- /dev/null +++ b/Tests/AFTestURLProtocol.m @@ -0,0 +1,98 @@ +// AFTestURLProtocol.m +// +// Copyright (c) 2011 Gowalla (http://gowalla.com/) +// +// 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: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// 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 "AFTestURLProtocol.h" + +typedef id(^AFTestURLProtocolInitializationCallback)(AFTestURLProtocol *protocol); +static NSURL *matchingURL = nil; +static AFTestURLProtocolInitializationCallback initializationCallback = NULL; + + + +@implementation AFTestURLProtocol + ++ (void)matchURL:(NSURL *)URL withCallback:(id(^)(AFTestURLProtocol *protocol))callback { + matchingURL = URL; + initializationCallback = callback; +} + ++ (void)load { + [NSURLProtocol registerClass:[AFTestURLProtocol class]]; +} + +#pragma mark - NSURLProtocol + ++ (BOOL)canInitWithRequest:(NSURLRequest *)request { + return [request.URL isEqual:matchingURL] && initializationCallback; +} + ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { + return request; +} + ++ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { + return NO; +} + +- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id)client { + NSParameterAssert(initializationCallback); + + if (self = [super initWithRequest:request cachedResponse:cachedResponse client:client]) { + self = initializationCallback(self); + + matchingURL = nil; + initializationCallback = NULL; + } + return self; +} + +- (void)startLoading { + +} + +- (void)stopLoading { + +} + +#pragma mark - NSURLAuthenticationChallengeSender + +- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + +} + +- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + +} + +- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + +} + +- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { + +} + +- (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge { + +} + +@end diff --git a/Tests/AFURLConnectionOperationTests.m b/Tests/AFURLConnectionOperationTests.m new file mode 100644 index 0000000..b399d7b --- /dev/null +++ b/Tests/AFURLConnectionOperationTests.m @@ -0,0 +1,60 @@ +// +// AFJSONRequestOperationTests.m +// AFNetworking Tests +// +// Created by Kevin Harwood on 5/16/13. +// Copyright (c) 2013 AFNetworking. All rights reserved. +// + +#import "AFNetworkingTests.h" +#import "AFURLConnectionOperation.h" +#import "AFTestURLProtocol.h" +#import "OCMock.h" + +@interface AFURLConnectionOperationTests : SenTestCase + +@property (readwrite, nonatomic, strong) NSURL *baseURL; + +@end + + + +@implementation AFURLConnectionOperationTests +@synthesize baseURL = _baseURL; + +- (void)setUp { + self.baseURL = [NSURL URLWithString:AFNetworkingTestsBaseURLString]; +} + +- (void)testThatAFURLConnectionOperationInvokesWillSendRequestForAuthenticationChallengeBlock { + NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"/path" relativeToURL:self.baseURL]]; + AFURLConnectionOperation *operation = [[AFURLConnectionOperation alloc] initWithRequest:request]; + + __block BOOL willSendRequestForAuthenticationChallengeBlockInvoked = NO; + [operation setWillSendRequestForAuthenticationChallengeBlock:^(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge) { + willSendRequestForAuthenticationChallengeBlockInvoked = YES; + }]; + + [AFTestURLProtocol matchURL:request.URL withCallback:^id(AFTestURLProtocol *protocol) { + id mockedProtocol = [OCMockObject partialMockForObject:protocol]; + + void(^startOperation)(NSInvocation *invocation) = ^(NSInvocation *invocation) { + __unsafe_unretained AFTestURLProtocol *protocol = nil; + [invocation getArgument:&protocol atIndex:0]; + + NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:request.URL.host port:request.URL.port.integerValue protocol:request.URL.scheme realm:nil authenticationMethod:NSURLAuthenticationMethodDefault]; + NSURLAuthenticationChallenge *authenticationChallenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:protectionSpace proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:protocol]; + [protocol.client URLProtocol:protocol didReceiveAuthenticationChallenge:authenticationChallenge]; + }; + [[[mockedProtocol stub] andDo:startOperation] startLoading]; + + return mockedProtocol; + }]; + + [operation start]; + expect(willSendRequestForAuthenticationChallengeBlockInvoked).will.beTruthy(); + + [operation cancel]; +} + +@end