Ben Copsey

Added basic unit tests

Added responseStatusCode property
Various cleanups
... ... @@ -26,9 +26,12 @@
//Files that will be POSTed to the url
NSMutableDictionary *fileData;
//Dictionary for custom request headers
//Dictionary for custom HTTP request headers
NSMutableDictionary *requestHeaders;
//Will be populate with HTTP response headers from the server
NSDictionary *responseHeaders;
//If useKeychainPersistance is true, network requests will attempt to read credentials from the keychain, and will save them in the keychain when they are successfully presented
BOOL useKeychainPersistance;
... ... @@ -72,11 +75,15 @@
CFReadStreamRef readStream;
// Authentication currently being used for prompting and resuming
CFHTTPAuthenticationRef authentication;
CFHTTPAuthenticationRef requestAuthentication;
NSMutableDictionary *requestCredentials;
// Credentials associated with the authentication (reused until server says no)
//CFMutableDictionaryRef credentials;
// HTTP status code, eg: 200 = OK, 404 = Not found etc
int responseStatusCode;
//Size of the response
double contentLength;
... ... @@ -103,8 +110,6 @@
//Called on the delegate when the request fails
SEL didFailSelector;
NSDictionary *responseHeaders;
NSMutableDictionary *requestCredentials;
}
... ... @@ -113,9 +118,6 @@
// Should be an HTTP or HTTPS url, may include username and password if appropriate
- (id)initWithURL:(NSURL *)newURL;
#pragma mark delegate configuration
#pragma mark setup request
//Add a custom header to the request
... ... @@ -159,10 +161,10 @@
#pragma mark handling request complete / failure
//Called when a request completes successfully - defaults to: @selector(requestFinished:)
// Called when a request completes successfully - defaults to: @selector(requestFinished:)
- (void)requestFinished;
//Called when a request fails - defaults to: @selector(requestFailed:)
// Called when a request fails - defaults to: @selector(requestFailed:)
- (void)failWithProblem:(NSString *)problem;
#pragma mark http authentication stuff
... ... @@ -170,6 +172,12 @@
// Reads the response headers to find the content length, and returns true if the request needs a username and password (or if those supplied were incorrect)
- (BOOL)readResponseHeadersReturningAuthenticationFailure;
// Apply credentials to this request
- (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials;
// Attempt to obtain credentials for this request from the URL, username and password or keychain
- (NSMutableDictionary *)findCredentials;
// Unlock (unpause) the request thread so it can resume the request
// Should be called by delegates when they have populated the authentication information after an authentication challenge
- (void)retryWithAuthentication;
... ... @@ -188,16 +196,23 @@
- (void)handleStreamComplete;
- (void)handleStreamError;
#pragma mark managing the session
+ (void)setSessionCredentials:(NSMutableDictionary *)newCredentials;
+ (void)setSessionAuthentication:(CFHTTPAuthenticationRef)newAuthentication;
#pragma mark keychain storage
//Save credentials to the keychain
// Save credentials for this request to the keychain
- (void)saveCredentialsToKeychain:(NSMutableDictionary *)newCredentials;
// Save creddentials to the keychain
+ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
//Return credentials from the keychain
// Return credentials from the keychain
+ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
//Remove credentials from the keychain
// Remove credentials from the keychain
+ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm;
... ... @@ -215,9 +230,7 @@
@property (assign,readonly) BOOL complete;
@property (retain) NSDictionary *responseHeaders;
@property (retain) NSDictionary *requestCredentials;
- (void)saveCredentialsToKeychain:(NSMutableDictionary *)newCredentials;
- (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials;
@property (assign) int responseStatusCode;
@end
... ...
... ... @@ -33,12 +33,6 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
- (id)initWithURL:(NSURL *)newURL
{
[self init];
url = [newURL retain];
return self;
}
- (id)init {
[super init];
lastBytesSent = 0;
postData = nil;
... ... @@ -48,7 +42,7 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
requestHeaders = nil;
authenticationRealm = nil;
outputStream = nil;
authentication = NULL;
requestAuthentication = NULL;
//credentials = NULL;
request = NULL;
responseHeaders = nil;
... ... @@ -57,13 +51,14 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
didFinishSelector = @selector(requestFinished:);
didFailSelector = @selector(requestFailed:);
delegate = nil;
return self;
url = [newURL retain];
return self;
}
- (void)dealloc
{
if (authentication) {
CFRelease(authentication);
if (requestAuthentication) {
CFRelease(requestAuthentication);
}
if (request) {
CFRelease(request);
... ... @@ -146,12 +141,6 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
#pragma mark request logic
+ (void)setSessionCredentials:(NSMutableDictionary *)newCredentials
{
[sessionCredentials release];
sessionCredentials = [newCredentials retain];
}
// Create the request
- (void)main
{
... ... @@ -173,8 +162,7 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
//If we've already talked to this server and have valid credentials, let's apply them to the request
if (useSessionPersistance && sessionCredentials && sessionAuthentication) {
if (!CFHTTPMessageApplyCredentialDictionary(request, sessionAuthentication, (CFMutableDictionaryRef)sessionCredentials, NULL)) {
CFRelease(sessionAuthentication);
sessionAuthentication = NULL;
[ASIHTTPRequest setSessionAuthentication:NULL];
[ASIHTTPRequest setSessionCredentials:nil];
}
}
... ... @@ -415,9 +403,10 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
if (CFHTTPMessageIsHeaderComplete(headers)) {
responseHeaders = (NSDictionary *)CFHTTPMessageCopyAllHeaderFields(headers);
responseStatusCode = CFHTTPMessageGetResponseStatusCode(headers);
// Is the server response a challenge for credentials?
isAuthenticationChallenge = (CFHTTPMessageGetResponseStatusCode(headers) == 401);
isAuthenticationChallenge = (responseStatusCode == 401);
//We won't reset the download progress delegate if we got an authentication challenge
if (!isAuthenticationChallenge) {
... ... @@ -438,13 +427,6 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
}
// Called by delegate to resume loading once authentication info has been populated
- (void)retryWithAuthentication
{
[authenticationLock lockWhenCondition:1];
[authenticationLock unlockWithCondition:2];
}
- (void)saveCredentialsToKeychain:(NSMutableDictionary *)newCredentials
{
NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername]
... ... @@ -458,20 +440,16 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
- (BOOL)applyCredentials:(NSMutableDictionary *)newCredentials
{
if (newCredentials && authentication && request) {
if (newCredentials && requestAuthentication && request) {
// Apply whatever credentials we've built up to the old request
if (CFHTTPMessageApplyCredentialDictionary(request, authentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
//If we have credentials and they're ok, let's save them to the keychain
if (useKeychainPersistance) {
[self saveCredentialsToKeychain:newCredentials];
}
if (useSessionPersistance) {
if (sessionAuthentication) {
CFRelease(sessionAuthentication);
}
sessionAuthentication = authentication;
CFRetain(sessionAuthentication);
[ASIHTTPRequest setSessionAuthentication:requestAuthentication];
[ASIHTTPRequest setSessionCredentials:newCredentials];
}
[self setRequestCredentials:newCredentials];
... ... @@ -481,15 +459,15 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
return FALSE;
}
- (NSMutableDictionary *)getCredentials
- (NSMutableDictionary *)findCredentials
{
NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
// Get the authentication realm
[authenticationRealm release];
authenticationRealm = nil;
if (!CFHTTPAuthenticationRequiresAccountDomain(authentication)) {
authenticationRealm = (NSString *)CFHTTPAuthenticationCopyRealm(authentication);
if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
authenticationRealm = (NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication);
}
//First, let's look at the url to see if the username and password were included
... ... @@ -519,30 +497,37 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
[newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
return newCredentials;
}
return NULL;
return nil;
}
// Called by delegate to resume loading once authentication info has been populated
- (void)retryWithAuthentication
{
[authenticationLock lockWhenCondition:1];
[authenticationLock unlockWithCondition:2];
}
- (void)attemptToApplyCredentialsAndResume
{
//Read authentication data
if (!authentication) {
if (!requestAuthentication) {
CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader);
authentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
CFRelease(responseHeader);
}
if (!authentication) {
if (!requestAuthentication) {
[self failWithProblem:@"Failed to get authentication object from response headers"];
return;
}
//See if authentication is valid
CFStreamError err;
if (!CFHTTPAuthenticationIsValid(authentication, &err)) {
if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) {
CFRelease(authentication);
authentication = NULL;
CFRelease(requestAuthentication);
requestAuthentication = NULL;
// check for bad credentials, so we can give the delegate a chance to replace them
if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
... ... @@ -572,9 +557,9 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
}
// are a user name & password needed?
} else if (CFHTTPAuthenticationRequiresUserNameAndPassword(authentication)) {
} else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) {
NSMutableDictionary *newCredentials = [self getCredentials];
NSMutableDictionary *newCredentials = [self findCredentials];
//If we have some credentials to use let's apply them to the request and continue
if (newCredentials) {
... ... @@ -714,6 +699,24 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
}
}
#pragma mark managing the session
+ (void)setSessionCredentials:(NSMutableDictionary *)newCredentials
{
[sessionCredentials release];
sessionCredentials = [newCredentials retain];
}
+ (void)setSessionAuthentication:(CFHTTPAuthenticationRef)newAuthentication
{
if (sessionAuthentication) {
CFRelease(sessionAuthentication);
}
sessionAuthentication = newAuthentication;
if (newAuthentication) {
CFRetain(sessionAuthentication);
}
}
#pragma mark keychain storage
... ... @@ -774,4 +777,5 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
@synthesize complete;
@synthesize responseHeaders;
@synthesize requestCredentials;
@synthesize responseStatusCode;
@end
... ...
//
// ASIHTTPRequestTests.h
// asi-http-request
//
// Created by Ben Copsey on 01/08/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import <SenTestingKit/SenTestingKit.h>
@interface ASIHTTPRequestTests : SenTestCase {
}
- (void)testBasicDownload;
@end
... ...
//
// ASIHTTPRequestTests.m
// asi-http-request
//
// Created by Ben Copsey on 01/08/2008.
// Copyright 2008 All-Seeing Interactive. All rights reserved.
//
#import "ASIHTTPRequestTests.h"
#import "ASIHTTPRequest.h"
@implementation ASIHTTPRequestTests
- (void)testBasicDownload
{
//Grab data
NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com"] autorelease];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
NSString *html = [request dataString];
STAssertNotNil(html,@"Basic synchronous request failed");
//Check we're getting the correct response headers
NSString *pingBackHeader = [[request responseHeaders] objectForKey:@"X-Pingback"];
BOOL success = [pingBackHeader isEqualToString:@"http://allseeing-i.com/Ping-Back"];
STAssertTrue(success,@"Failed to populate response headers");
//Check we're getting back the correct status code
url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/a-page-that-does-not-exist"] autorelease];
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
success = ([request responseStatusCode] == 404);
STAssertTrue(success,@"Didn't get correct status code");
//Check data
NSRange notFound = NSMakeRange(NSNotFound, 0);
success = !NSEqualRanges([html rangeOfString:@"All-Seeing Interactive"],notFound);
STAssertTrue(success,@"Failed to download the correct data");
//Attempt to grab from bad url (astonishingly, there is a website at http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com !)
url = [[[NSURL alloc] initWithString:@"http://aaaaaaaaaaaaaaaaaaaaaaaaaaaaab.com"] autorelease];
request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request start];
NSError *error = [request error];
STAssertNotNil(error,@"Failed to generate an error for a bad host");
}
@end
... ...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
... ...
This diff is collapsed. Click to expand it.
This diff could not be displayed because it is too large.
This diff was suppressed by a .gitattributes entry.