Permalink
Browse files

Merge branch 'master' into s3

  • Loading branch information...
2 parents 7eda18b + 4a8954f commit 6413371b44c7656883b776d9499f638cc8e5a599 @pokeb committed Jul 13, 2009
View
@@ -26,7 +26,8 @@ typedef enum _ASINetworkErrorType {
ASIUnableToCreateRequestErrorType = 5,
ASIInternalErrorWhileBuildingRequestType = 6,
ASIInternalErrorWhileApplyingCredentialsType = 7,
- ASIFileManagementError = 8
+ ASIFileManagementError = 8,
+ ASITooMuchRedirectionErrorType = 9
} ASINetworkErrorType;
@@ -218,6 +219,12 @@ extern NSString* const NetworkRequestErrorDomain;
// When YES, requests will automatically redirect when they get a HTTP 30x header (defaults to YES)
BOOL shouldRedirect;
+ // Used internally to tell the main loop we need to stop and retry with a new url
+ BOOL needsRedirect;
+
+ // Incremented every time this request redirects. When it reaches 5, we give up
+ int redirectCount;
+
// When NO, requests will not check the secure certificate is valid (use for self-signed cerficates during development, DO NOT USE IN PRODUCTION) Default is YES
BOOL validatesSecureCertificate;
@@ -297,7 +304,9 @@ extern NSString* const NetworkRequestErrorDomain;
#pragma mark http authentication stuff
-// 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)
+// Reads the response headers to find the content length, encoding, cookies for the session
+// Also initiates request redirection when shouldRedirect is true
+// Returns true if the request needs a username and password (or if those supplied were incorrect)
- (BOOL)readResponseHeadersReturningAuthenticationFailure;
// Apply credentials to this request
@@ -345,6 +354,9 @@ extern NSString* const NetworkRequestErrorDomain;
+ (void)setSessionCookies:(NSMutableArray *)newSessionCookies;
+ (NSMutableArray *)sessionCookies;
+// Adds a cookie to our list of cookies we've accepted, checking first for an old version of the same cookie and removing that
++ (void)addSessionCookie:(NSHTTPCookie *)newCookie;
+
// Dump all session data (authentication and cookies)
+ (void)clearSession;
View
@@ -27,6 +27,8 @@
static NSMutableDictionary *sessionCredentials = nil;
static NSMutableArray *sessionCookies = nil;
+// The number of times we will allow requests to redirect before we fail with a redirection error
+const int RedirectionLimit = 5;
static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
[((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type];
@@ -39,6 +41,8 @@ static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventTy
static NSError *ASIRequestTimedOutError;
static NSError *ASIAuthenticationError;
static NSError *ASIUnableToCreateRequestError;
+static NSError *ASITooMuchRedirectionError;
+
// Private stuff
@interface ASIHTTPRequest ()
@@ -64,6 +68,8 @@ @interface ASIHTTPRequest ()
@property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream;
@property (assign, nonatomic) int authenticationRetryCount;
@property (assign, nonatomic) BOOL updatedProgress;
+ @property (assign, nonatomic) BOOL needsRedirect;
+ @property (assign, nonatomic) int redirectCount;
@end
@implementation ASIHTTPRequest
@@ -80,6 +86,8 @@ + (void)initialize
ASIAuthenticationError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]] retain];
ASIRequestCancelledError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]] retain];
ASIUnableToCreateRequestError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]] retain];
+ ASITooMuchRedirectionError = [[NSError errorWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]] retain];
+
}
[super initialize];
}
@@ -436,10 +444,7 @@ - (void)startRequest
[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
return;
}
-
- // Tell CFNetwork to automatically redirect for 30x status codes
- CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPShouldAutoredirect, [self shouldRedirect] ? kCFBooleanTrue : kCFBooleanFalse);
-
+
// Tell CFNetwork not to validate SSL certificates
if (!validatesSecureCertificate) {
CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]);
@@ -517,7 +522,7 @@ - (void)loadRequest
// See if we need to timeout
if (lastActivityTime && timeOutSeconds > 0 && [now timeIntervalSinceDate:lastActivityTime] > timeOutSeconds) {
- // Prevent timeouts before 128KB has been sent when the size of data to upload is greater than 128KB
+ // Prevent timeouts before 128KB* has been sent when the size of data to upload is greater than 128KB* (*32KB on iPhone 3.0 SDK)
// This is to workaround the fact that kCFStreamPropertyHTTPRequestBytesWrittenCount is the amount written to the buffer, not the amount actually sent
// This workaround prevents erroneous timeouts in low bandwidth situations (eg iPhone)
if (contentLength <= uploadBufferSize || (uploadBufferSize > 0 && totalBytesSent > uploadBufferSize)) {
@@ -528,6 +533,22 @@ - (void)loadRequest
}
}
+ // Do we need to redirect?
+ if ([self needsRedirect]) {
+ [self cancelLoad];
+ [self setNeedsRedirect:NO];
+ [self setRedirectCount:[self redirectCount]+1];
+ if ([self redirectCount] > RedirectionLimit) {
+ // Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool.
+ [self failWithError:ASITooMuchRedirectionError];
+ [self setComplete:YES];
+ } else {
+ // Go all the way back to the beginning and build the request again, so that we can apply any new cookies
+ [self main];
+ }
+ break;
+ }
+
// See if our NSOperationQueue told us to cancel
if ([self isCancelled]) {
break;
@@ -944,7 +965,7 @@ - (BOOL)readResponseHeadersReturningAuthenticationFailure
[self setResponseStatusCode:CFHTTPMessageGetResponseStatusCode(headers)];
// Is the server response a challenge for credentials?
- isAuthenticationChallenge = (responseStatusCode == 401);
+ isAuthenticationChallenge = ([self responseStatusCode] == 401);
// We won't reset the download progress delegate if we got an authentication challenge
if (!isAuthenticationChallenge) {
@@ -990,18 +1011,22 @@ - (BOOL)readResponseHeadersReturningAuthenticationFailure
NSArray *newCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseHeaders forURL:url];
[self setResponseCookies:newCookies];
- if (useCookiePersistance) {
+ if ([self useCookiePersistance]) {
// Store cookies in global persistent store
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:newCookies forURL:url mainDocumentURL:nil];
// We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later
- if (!sessionCookies) {
- [ASIHTTPRequest setSessionCookies:[[[NSMutableArray alloc] init] autorelease]];
- NSHTTPCookie *cookie;
- for (cookie in newCookies) {
- [[ASIHTTPRequest sessionCookies] addObject:cookie];
- }
+ NSHTTPCookie *cookie;
+ for (cookie in newCookies) {
+ [ASIHTTPRequest addSessionCookie:cookie];
+ }
+ }
+ // Do we need to redirect?
+ if ([self shouldRedirect]) {
+ if ([self responseStatusCode] > 300 && [self responseStatusCode] < 308 && [self responseStatusCode] != 304) {
+ [self setURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]];
+ [self setNeedsRedirect:YES];
}
}
@@ -1256,6 +1281,9 @@ - (void)handleBytesAvailable
return;
}
}
+ if ([self needsRedirect]) {
+ return;
+ }
int bufferSize = 2048;
if (contentLength > 262144) {
bufferSize = 65536;
@@ -1307,6 +1335,9 @@ - (void)handleStreamComplete
return;
}
}
+ if ([self needsRedirect]) {
+ return;
+ }
[progressLock lock];
[self setComplete:YES];
[self updateProgressIndicators];
@@ -1371,8 +1402,6 @@ - (void)handleStreamError
{
NSError *underlyingError = [(NSError *)CFReadStreamCopyError(readStream) autorelease];
-
-
[self cancelLoad];
[self setComplete:YES];
@@ -1459,19 +1488,37 @@ + (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSStr
+ (NSMutableArray *)sessionCookies
{
+ if (!sessionCookies) {
+ [ASIHTTPRequest setSessionCookies:[[[NSMutableArray alloc] init] autorelease]];
+ }
return sessionCookies;
}
+ (void)setSessionCookies:(NSMutableArray *)newSessionCookies
{
// Remove existing cookies from the persistent store
- for (NSHTTPCookie *cookie in [ASIHTTPRequest sessionCookies]) {
+ for (NSHTTPCookie *cookie in sessionCookies) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
}
[sessionCookies release];
sessionCookies = [newSessionCookies retain];
}
++ (void)addSessionCookie:(NSHTTPCookie *)newCookie
+{
+ NSHTTPCookie *cookie;
+ int i;
+ int max = [[ASIHTTPRequest sessionCookies] count];
+ for (i=0; i<max; i++) {
+ cookie = [[ASIHTTPRequest sessionCookies] objectAtIndex:i];
+ if ([[cookie domain] isEqualToString:[newCookie domain]] && [[cookie path] isEqualToString:[newCookie path]] && [[cookie name] isEqualToString:[newCookie name]]) {
+ [[ASIHTTPRequest sessionCookies] removeObjectAtIndex:i];
+ break;
+ }
+ }
+ [[ASIHTTPRequest sessionCookies] addObject:newCookie];
+}
+
// Dump all session data (authentication and cookies)
+ (void)clearSession
{
@@ -1676,4 +1723,6 @@ + (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest
@synthesize updatedProgress;
@synthesize shouldRedirect;
@synthesize validatesSecureCertificate;
+@synthesize needsRedirect;
+@synthesize redirectCount;
@end
@@ -33,4 +33,6 @@
- (void)testCompressedResponse;
- (void)testCompressedResponseDownloadToFile;
- (void)testSSL;
+- (void)testRedirectPreservesSession;
+- (void)testTooMuchRedirection;
@end
@@ -639,4 +639,24 @@ - (void)testSSL
GHAssertNil([request error],@"Failed to accept a self-signed certificate");
}
+- (void)testRedirectPreservesSession
+{
+ // Remove any old session cookies
+ [ASIHTTPRequest clearSession];
+ ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/session_redirect"]];
+ [request start];
+ BOOL success = [[request responseString] isEqualToString:@"Take me to your leader"];
+ GHAssertTrue(success,@"Failed to redirect preserving session cookies");
+}
+
+- (void)testTooMuchRedirection
+{
+ // This url will simply send a 302 redirect back to itself
+ ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/one_infinite_loop"]];
+ [request start];
+ GHAssertNotNil([request error],@"Failed to generate an error when redirection occurs too many times");
+ BOOL success = ([[request error] code] == ASITooMuchRedirectionErrorType);
+ GHAssertTrue(success,@"Generated the wrong error for a redirection loop");
+}
+
@end
View
@@ -100,6 +100,7 @@ - (IBAction)fetchThreeImages:(id)sender
[imageView3 setImage:nil];
[networkQueue cancelAllOperations];
+ [networkQueue setRequestDidFinishSelector:NULL];
[networkQueue setDownloadProgressDelegate:progressIndicator];
[networkQueue setDelegate:self];
[networkQueue setShowAccurateProgress:([showAccurateProgress state] == NSOnState)];

0 comments on commit 6413371

Please sign in to comment.