Ben Copsey

Fix cancelling for requests that redirect

@@ -366,6 +366,9 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; @@ -366,6 +366,9 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount;
366 // When set to YES, 301 and 302 automatic redirects will use the original method and and body, according to the HTTP 1.1 standard 366 // When set to YES, 301 and 302 automatic redirects will use the original method and and body, according to the HTTP 1.1 standard
367 // Default is NO (to follow the behaviour of most browsers) 367 // Default is NO (to follow the behaviour of most browsers)
368 BOOL shouldUseRFC2616RedirectBehaviour; 368 BOOL shouldUseRFC2616RedirectBehaviour;
  369 +
  370 + // Used internally to record when a request has finished downloading data
  371 + BOOL downloadComplete;
369 } 372 }
370 373
371 #pragma mark init / dealloc 374 #pragma mark init / dealloc
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 #import "ASIInputStream.h" 21 #import "ASIInputStream.h"
22 22
23 // Automatically set on build 23 // Automatically set on build
24 -NSString *ASIHTTPRequestVersion = @"v1.5-44 2010-02-04"; 24 +NSString *ASIHTTPRequestVersion = @"v1.5-45 2010-02-10";
25 25
26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 26 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
27 27
@@ -181,6 +181,7 @@ static BOOL isiPhoneOS2; @@ -181,6 +181,7 @@ static BOOL isiPhoneOS2;
181 @property (assign) ASIAuthenticationState authenticationNeeded; 181 @property (assign) ASIAuthenticationState authenticationNeeded;
182 @property (assign, nonatomic) BOOL readStreamIsScheduled; 182 @property (assign, nonatomic) BOOL readStreamIsScheduled;
183 @property (retain, nonatomic) NSTimer *statusTimer; 183 @property (retain, nonatomic) NSTimer *statusTimer;
  184 +@property (assign, nonatomic) BOOL downloadComplete;
184 @end 185 @end
185 186
186 187
@@ -768,6 +769,7 @@ static BOOL isiPhoneOS2; @@ -768,6 +769,7 @@ static BOOL isiPhoneOS2;
768 769
769 [self requestStarted]; 770 [self requestStarted];
770 771
  772 + [self setDownloadComplete:NO];
771 [self setComplete:NO]; 773 [self setComplete:NO];
772 [self setTotalBytesRead:0]; 774 [self setTotalBytesRead:0];
773 [self setLastBytesRead:0]; 775 [self setLastBytesRead:0];
@@ -2403,7 +2405,15 @@ static BOOL isiPhoneOS2; @@ -2403,7 +2405,15 @@ static BOOL isiPhoneOS2;
2403 2405
2404 [[self cancelledLock] unlock]; 2406 [[self cancelledLock] unlock];
2405 2407
2406 - if (![self inProgress]) { 2408 + if ([self downloadComplete] && [self needsRedirect]) {
  2409 + CFRunLoopStop(CFRunLoopGetCurrent());
  2410 + [self performRedirect];
  2411 + return;
  2412 + } else if ([self downloadComplete] && [self authenticationNeeded]) {
  2413 + CFRunLoopStop(CFRunLoopGetCurrent());
  2414 + [self attemptToApplyCredentialsAndResume];
  2415 + return;
  2416 + } else if (![self inProgress]) {
2407 [self setStatusTimer:nil]; 2417 [self setStatusTimer:nil];
2408 } 2418 }
2409 2419
@@ -2497,6 +2507,8 @@ static BOOL isiPhoneOS2; @@ -2497,6 +2507,8 @@ static BOOL isiPhoneOS2;
2497 NSLog(@"Request %@ finished downloading data",self); 2507 NSLog(@"Request %@ finished downloading data",self);
2498 #endif 2508 #endif
2499 2509
  2510 + [self setDownloadComplete:YES];
  2511 +
2500 if (![self responseHeaders]) { 2512 if (![self responseHeaders]) {
2501 [self readResponseHeaders]; 2513 [self readResponseHeaders];
2502 } 2514 }
@@ -2528,7 +2540,6 @@ static BOOL isiPhoneOS2; @@ -2528,7 +2540,6 @@ static BOOL isiPhoneOS2;
2528 if (decompressionStatus != Z_OK) { 2540 if (decompressionStatus != Z_OK) {
2529 fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed with code %hi",[self temporaryFileDownloadPath],decompressionStatus],NSLocalizedDescriptionKey,nil]]; 2541 fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed with code %hi",[self temporaryFileDownloadPath],decompressionStatus],NSLocalizedDescriptionKey,nil]];
2530 } 2542 }
2531 -  
2532 [self removeTemporaryDownloadFile]; 2543 [self removeTemporaryDownloadFile];
2533 } else { 2544 } else {
2534 2545
@@ -2549,6 +2560,7 @@ static BOOL isiPhoneOS2; @@ -2549,6 +2560,7 @@ static BOOL isiPhoneOS2;
2549 } 2560 }
2550 [self setTemporaryFileDownloadPath:nil]; 2561 [self setTemporaryFileDownloadPath:nil];
2551 } 2562 }
  2563 +
2552 } 2564 }
2553 } 2565 }
2554 [progressLock unlock]; 2566 [progressLock unlock];
@@ -2565,27 +2577,20 @@ static BOOL isiPhoneOS2; @@ -2565,27 +2577,20 @@ static BOOL isiPhoneOS2;
2565 [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:closeStreamTime] forKey:@"expires"]; 2577 [[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:closeStreamTime] forKey:@"expires"];
2566 [connectionsLock unlock]; 2578 [connectionsLock unlock];
2567 2579
2568 - if ([self needsRedirect]) {  
2569 - CFRunLoopStop(CFRunLoopGetCurrent());  
2570 - [self performRedirect];  
2571 - return;  
2572 - } else if ([self authenticationNeeded]) {  
2573 - CFRunLoopStop(CFRunLoopGetCurrent());  
2574 - [self attemptToApplyCredentialsAndResume];  
2575 - return;  
2576 - }  
2577 -  
2578 - if (fileError) {  
2579 - [self failWithError:fileError];  
2580 - } else {  
2581 - [self requestFinished];  
2582 - }  
2583 -  
2584 if (![self authenticationNeeded]) { 2580 if (![self authenticationNeeded]) {
2585 [self destroyReadStream]; 2581 [self destroyReadStream];
2586 } 2582 }
2587 2583
2588 - [self markAsFinished]; 2584 + if (![self needsRedirect] && ![self authenticationNeeded]) {
  2585 +
  2586 + if (fileError) {
  2587 + [self failWithError:fileError];
  2588 + } else {
  2589 + [self requestFinished];
  2590 + }
  2591 +
  2592 + [self markAsFinished];
  2593 + }
2589 } 2594 }
2590 2595
2591 - (void)markAsFinished 2596 - (void)markAsFinished
@@ -3720,4 +3725,5 @@ static BOOL isiPhoneOS2; @@ -3720,4 +3725,5 @@ static BOOL isiPhoneOS2;
3720 @synthesize readStreamIsScheduled; 3725 @synthesize readStreamIsScheduled;
3721 @synthesize statusTimer; 3726 @synthesize statusTimer;
3722 @synthesize shouldUseRFC2616RedirectBehaviour; 3727 @synthesize shouldUseRFC2616RedirectBehaviour;
  3728 +@synthesize downloadComplete;
3723 @end 3729 @end
@@ -78,8 +78,29 @@ @@ -78,8 +78,29 @@
78 [request startAsynchronous]; 78 [request startAsynchronous];
79 [request cancel]; 79 [request cancel];
80 GHAssertNotNil([request error],@"Failed to cancel the request"); 80 GHAssertNotNil([request error],@"Failed to cancel the request");
  81 +
  82 + // Test cancelling a redirected request works
  83 + // This test is probably unreliable on very slow or very fast connections, as it depends on being able to complete the first request (but not the second) in under 2 seconds
  84 + request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/cancel_redirect"]];
  85 + [request startAsynchronous];
  86 +
  87 + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
  88 + [request cancel];
  89 +
  90 + BOOL success = ([[[request url] absoluteString] isEqualToString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel.txt"]);
  91 +
  92 + GHAssertTrue(success, @"Request did not redirect quickly enough, cannot proceed with test");
  93 +
  94 + GHAssertNotNil([request error],@"Failed to cancel the request");
  95 +
  96 + success = [request totalBytesRead] < 7900198;
  97 + GHAssertTrue(success, @"Downloaded the whole of the response even though we should have cancelled by now");
  98 +
  99 +
81 } 100 }
82 101
  102 +
  103 +
83 - (void)testDelegateMethods 104 - (void)testDelegateMethods
84 { 105 {
85 // We run this test on the main thread because otherwise we can't depend on the delegate being notified before we need to test it's working 106 // We run this test on the main thread because otherwise we can't depend on the delegate being notified before we need to test it's working