Statistics
| Branch: | Tag: | Revision:

root / Classes / ASIHTTPRequest.m @ 45f2fce6

History | View | Annotate | Download (161 kB)

1
//
2
//  ASIHTTPRequest.m
3
//
4
//  Created by Ben Copsey on 04/10/2007.
5
//  Copyright 2007-2010 All-Seeing Interactive. All rights reserved.
6
//
7
//  A guide to the main features is available at:
8
//  http://allseeing-i.com/ASIHTTPRequest
9
//
10
//  Portions are based on the ImageClient example from Apple:
11
//  See: http://developer.apple.com/samplecode/ImageClient/listing37.html
12

    
13
#import "ASIHTTPRequest.h"
14

    
15
#if TARGET_OS_IPHONE
16
#import "Reachability.h"
17
#import "ASIAuthenticationDialog.h"
18
#import <MobileCoreServices/MobileCoreServices.h>
19
#else
20
#import <SystemConfiguration/SystemConfiguration.h>
21
#endif
22
#import "ASIInputStream.h"
23
#import "ASIDataDecompressor.h"
24
#import "ASIDataCompressor.h"
25

    
26
// Automatically set on build
27
NSString *ASIHTTPRequestVersion = @"v1.8-4 2010-11-20";
28

    
29
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
30

    
31
static NSString *ASIHTTPRequestRunLoopMode = @"ASIHTTPRequestRunLoopMode";
32

    
33
static const CFOptionFlags kNetworkEvents =  kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred;
34

    
35
// In memory caches of credentials, used on when useSessionPersistence is YES
36
static NSMutableArray *sessionCredentialsStore = nil;
37
static NSMutableArray *sessionProxyCredentialsStore = nil;
38

    
39
// This lock mediates access to session credentials
40
static NSRecursiveLock *sessionCredentialsLock = nil;
41

    
42
// We keep track of cookies we have received here so we can remove them from the sharedHTTPCookieStorage later
43
static NSMutableArray *sessionCookies = nil;
44

    
45
// The number of times we will allow requests to redirect before we fail with a redirection error
46
const int RedirectionLimit = 5;
47

    
48
// The default number of seconds to use for a timeout
49
static NSTimeInterval defaultTimeOutSeconds = 10;
50

    
51
static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
52
    [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type];
53
}
54

    
55
// This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa
56
static NSRecursiveLock *progressLock;
57

    
58
static NSError *ASIRequestCancelledError;
59
static NSError *ASIRequestTimedOutError;
60
static NSError *ASIAuthenticationError;
61
static NSError *ASIUnableToCreateRequestError;
62
static NSError *ASITooMuchRedirectionError;
63

    
64
static NSMutableArray *bandwidthUsageTracker = nil;
65
static unsigned long averageBandwidthUsedPerSecond = 0;
66

    
67

    
68
// These are used for queuing persistent connections on the same connection
69

    
70
// Incremented every time we specify we want a new connection
71
static unsigned int nextConnectionNumberToCreate = 0;
72

    
73
// An array of connectionInfo dictionaries.
74
// When attempting a persistent connection, we look here to try to find an existing connection to the same server that is currently not in use
75
static NSMutableArray *persistentConnectionsPool = nil;
76

    
77
// Mediates access to the persistent connections pool
78
static NSRecursiveLock *connectionsLock = nil;
79

    
80
// Each request gets a new id, we store this rather than a ref to the request itself in the connectionInfo dictionary.
81
// We do this so we don't have to keep the request around while we wait for the connection to expire
82
static unsigned int nextRequestID = 0;
83

    
84
// Records how much bandwidth all requests combined have used in the last second
85
static unsigned long bandwidthUsedInLastSecond = 0; 
86

    
87
// A date one second in the future from the time it was created
88
static NSDate *bandwidthMeasurementDate = nil;
89

    
90
// Since throttling variables are shared among all requests, we'll use a lock to mediate access
91
static NSLock *bandwidthThrottlingLock = nil;
92

    
93
// the maximum number of bytes that can be transmitted in one second
94
static unsigned long maxBandwidthPerSecond = 0;
95

    
96
// A default figure for throttling bandwidth on mobile devices
97
unsigned long const ASIWWANBandwidthThrottleAmount = 14800;
98

    
99
#if TARGET_OS_IPHONE
100
// YES when bandwidth throttling is active
101
// This flag does not denote whether throttling is turned on - rather whether it is currently in use
102
// It will be set to NO when throttling was turned on with setShouldThrottleBandwidthForWWAN, but a WI-FI connection is active
103
static BOOL isBandwidthThrottled = NO;
104

    
105
// When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS)
106
// Wifi will not be throttled
107
static BOOL shouldThrottleBandwithForWWANOnly = NO;
108
#endif
109

    
110
// Mediates access to the session cookies so requests
111
static NSRecursiveLock *sessionCookiesLock = nil;
112

    
113
// This lock ensures delegates only receive one notification that authentication is required at once
114
// When using ASIAuthenticationDialogs, it also ensures only one dialog is shown at once
115
// If a request can't acquire the lock immediately, it means a dialog is being shown or a delegate is handling the authentication challenge
116
// Once it gets the lock, it will try to look for existing credentials again rather than showing the dialog / notifying the delegate
117
// This is so it can make use of any credentials supplied for the other request, if they are appropriate
118
static NSRecursiveLock *delegateAuthenticationLock = nil;
119

    
120
// When throttling bandwidth, Set to a date in future that we will allow all requests to wake up and reschedule their streams
121
static NSDate *throttleWakeUpTime = nil;
122

    
123
static id <ASICacheDelegate> defaultCache = nil;
124

    
125

    
126
// Used for tracking when requests are using the network
127
static unsigned int runningRequestCount = 0;
128

    
129

    
130
// You can use [ASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO] if you want to manage it yourself
131
// Alternatively, override showNetworkActivityIndicator / hideNetworkActivityIndicator
132
// By default this does nothing on Mac OS X, but again override the above methods for a different behaviour
133
static BOOL shouldUpdateNetworkActivityIndicator = YES;
134

    
135

    
136
//**Queue stuff**/
137

    
138
// The thread all requests will run on
139
// Hangs around forever, but will be blocked unless there are requests underway
140
static NSThread *networkThread = nil;
141

    
142
static NSOperationQueue *sharedQueue = nil;
143

    
144
// Private stuff
145
@interface ASIHTTPRequest ()
146

    
147
- (void)cancelLoad;
148

    
149
- (void)destroyReadStream;
150
- (void)scheduleReadStream;
151
- (void)unscheduleReadStream;
152

    
153
- (BOOL)willAskDelegateForCredentials;
154
- (BOOL)willAskDelegateForProxyCredentials;
155
- (void)askDelegateForProxyCredentials;
156
- (void)askDelegateForCredentials;
157
- (void)failAuthentication;
158

    
159
+ (void)measureBandwidthUsage;
160
+ (void)recordBandwidthUsage;
161

    
162
- (void)startRequest;
163
- (void)updateStatus:(NSTimer *)timer;
164
- (void)checkRequestStatus;
165
- (void)markAsFinished;
166
- (void)performRedirect;
167
- (BOOL)shouldTimeOut;
168

    
169
+ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease;
170
+ (void)hideNetworkActivityIndicatorAfterDelay;
171
+ (void)hideNetworkActivityIndicatorIfNeeeded;
172
+ (void)runRequests;
173

    
174

    
175
- (void)useDataFromCache;
176

    
177
// Called to update the size of a partial download when starting a request, or retrying after a timeout
178
- (void)updatePartialDownloadSize;
179

    
180
#if TARGET_OS_IPHONE
181
+ (void)registerForNetworkReachabilityNotifications;
182
+ (void)unsubscribeFromNetworkReachabilityNotifications;
183
// Called when the status of the network changes
184
+ (void)reachabilityChanged:(NSNotification *)note;
185
#endif
186

    
187
#if NS_BLOCKS_AVAILABLE
188
- (void)performBlockOnMainThread:(ASIBasicBlock)block;
189
- (void)releaseBlocksOnMainThread;
190
+ (void)releaseBlocks:(NSArray *)blocks;
191
- (void)callBlock:(ASIBasicBlock)block;
192
#endif
193

    
194

    
195

    
196

    
197

    
198
@property (assign) BOOL complete;
199
@property (retain) NSArray *responseCookies;
200
@property (assign) int responseStatusCode;
201
@property (retain, nonatomic) NSDate *lastActivityTime;
202

    
203
@property (assign) unsigned long long partialDownloadSize;
204
@property (assign, nonatomic) unsigned long long uploadBufferSize;
205
@property (retain, nonatomic) NSOutputStream *postBodyWriteStream;
206
@property (retain, nonatomic) NSInputStream *postBodyReadStream;
207
@property (assign, nonatomic) unsigned long long lastBytesRead;
208
@property (assign, nonatomic) unsigned long long lastBytesSent;
209
@property (retain) NSRecursiveLock *cancelledLock;
210
@property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream;
211
@property (retain, nonatomic) NSOutputStream *inflatedFileDownloadOutputStream;
212
@property (assign) int authenticationRetryCount;
213
@property (assign) int proxyAuthenticationRetryCount;
214
@property (assign, nonatomic) BOOL updatedProgress;
215
@property (assign, nonatomic) BOOL needsRedirect;
216
@property (assign, nonatomic) int redirectCount;
217
@property (retain, nonatomic) NSData *compressedPostBody;
218
@property (retain, nonatomic) NSString *compressedPostBodyFilePath;
219
@property (retain) NSString *authenticationRealm;
220
@property (retain) NSString *proxyAuthenticationRealm;
221
@property (retain) NSString *responseStatusMessage;
222
@property (assign) BOOL inProgress;
223
@property (assign) int retryCount;
224
@property (assign) BOOL connectionCanBeReused;
225
@property (retain, nonatomic) NSMutableDictionary *connectionInfo;
226
@property (retain, nonatomic) NSInputStream *readStream;
227
@property (assign) ASIAuthenticationState authenticationNeeded;
228
@property (assign, nonatomic) BOOL readStreamIsScheduled;
229
@property (assign, nonatomic) BOOL downloadComplete;
230
@property (retain) NSNumber *requestID;
231
@property (assign, nonatomic) NSString *runLoopMode;
232
@property (retain, nonatomic) NSTimer *statusTimer;
233
@property (assign) BOOL didUseCachedResponse;
234
@property (retain, nonatomic) NSURL *redirectURL;
235
@end
236

    
237

    
238
@implementation ASIHTTPRequest
239

    
240
#pragma mark init / dealloc
241

    
242
+ (void)initialize
243
{
244
	if (self == [ASIHTTPRequest class]) {
245
		persistentConnectionsPool = [[NSMutableArray alloc] init];
246
		connectionsLock = [[NSRecursiveLock alloc] init];
247
		progressLock = [[NSRecursiveLock alloc] init];
248
		bandwidthThrottlingLock = [[NSLock alloc] init];
249
		sessionCookiesLock = [[NSRecursiveLock alloc] init];
250
		sessionCredentialsLock = [[NSRecursiveLock alloc] init];
251
		delegateAuthenticationLock = [[NSRecursiveLock alloc] init];
252
		bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5];
253
		ASIRequestTimedOutError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]];  
254
		ASIAuthenticationError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]];
255
		ASIRequestCancelledError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]];
256
		ASIUnableToCreateRequestError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]];
257
		ASITooMuchRedirectionError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]];
258
		sharedQueue = [[NSOperationQueue alloc] init];
259
		[sharedQueue setMaxConcurrentOperationCount:4];
260

    
261
	}
262
}
263

    
264

    
265
- (id)initWithURL:(NSURL *)newURL
266
{
267
	self = [self init];
268
	[self setRequestMethod:@"GET"];
269

    
270
	[self setRunLoopMode:NSDefaultRunLoopMode];
271
	[self setShouldAttemptPersistentConnection:YES];
272
	[self setPersistentConnectionTimeoutSeconds:60.0];
273
	[self setShouldPresentCredentialsBeforeChallenge:YES];
274
	[self setShouldRedirect:YES];
275
	[self setShowAccurateProgress:YES];
276
	[self setShouldResetDownloadProgress:YES];
277
	[self setShouldResetUploadProgress:YES];
278
	[self setAllowCompressedResponse:YES];
279
	[self setShouldWaitToInflateCompressedResponses:YES];
280
	[self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
281
	[self setShouldPresentProxyAuthenticationDialog:YES];
282
	
283
	[self setTimeOutSeconds:[ASIHTTPRequest defaultTimeOutSeconds]];
284
	[self setUseSessionPersistence:YES];
285
	[self setUseCookiePersistence:YES];
286
	[self setValidatesSecureCertificate:YES];
287
	[self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]];
288
	[self setDidStartSelector:@selector(requestStarted:)];
289
	[self setDidReceiveResponseHeadersSelector:@selector(request:didReceiveResponseHeaders:)];
290
	[self setWillRedirectSelector:@selector(request:willRedirectToURL:)];
291
	[self setDidFinishSelector:@selector(requestFinished:)];
292
	[self setDidFailSelector:@selector(requestFailed:)];
293
	[self setDidReceiveDataSelector:@selector(request:didReceiveData:)];
294
	[self setURL:newURL];
295
	[self setCancelledLock:[[[NSRecursiveLock alloc] init] autorelease]];
296
	[self setDownloadCache:[[self class] defaultCache]];
297
	return self;
298
}
299

    
300
+ (id)requestWithURL:(NSURL *)newURL
301
{
302
	return [[[self alloc] initWithURL:newURL] autorelease];
303
}
304

    
305
+ (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache
306
{
307
	return [self requestWithURL:newURL usingCache:cache andCachePolicy:ASIUseDefaultCachePolicy];
308
}
309

    
310
+ (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache andCachePolicy:(ASICachePolicy)policy
311
{
312
	ASIHTTPRequest *request = [[[self alloc] initWithURL:newURL] autorelease];
313
	[request setDownloadCache:cache];
314
	[request setCachePolicy:policy];
315
	return request;
316
}
317

    
318
- (void)dealloc
319
{
320
	[self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
321
	if (requestAuthentication) {
322
		CFRelease(requestAuthentication);
323
	}
324
	if (proxyAuthentication) {
325
		CFRelease(proxyAuthentication);
326
	}
327
	if (request) {
328
		CFRelease(request);
329
	}
330
	if (clientCertificateIdentity) {
331
		CFRelease(clientCertificateIdentity);
332
	}
333
	[self cancelLoad];
334
	[queue release];
335
	[userInfo release];
336
	[postBody release];
337
	[compressedPostBody release];
338
	[error release];
339
	[requestHeaders release];
340
	[requestCookies release];
341
	[downloadDestinationPath release];
342
	[temporaryFileDownloadPath release];
343
	[temporaryUncompressedDataDownloadPath release];
344
	[fileDownloadOutputStream release];
345
	[inflatedFileDownloadOutputStream release];
346
	[username release];
347
	[password release];
348
	[domain release];
349
	[authenticationRealm release];
350
	[authenticationScheme release];
351
	[requestCredentials release];
352
	[proxyHost release];
353
	[proxyType release];
354
	[proxyUsername release];
355
	[proxyPassword release];
356
	[proxyDomain release];
357
	[proxyAuthenticationRealm release];
358
	[proxyAuthenticationScheme release];
359
	[proxyCredentials release];
360
	[url release];
361
	[originalURL release];
362
	[lastActivityTime release];
363
	[responseCookies release];
364
	[rawResponseData release];
365
	[responseHeaders release];
366
	[requestMethod release];
367
	[cancelledLock release];
368
	[postBodyFilePath release];
369
	[compressedPostBodyFilePath release];
370
	[postBodyWriteStream release];
371
	[postBodyReadStream release];
372
	[PACurl release];
373
	[clientCertificates release];
374
	[responseStatusMessage release];
375
	[connectionInfo release];
376
	[requestID release];
377
	[dataDecompressor release];
378

    
379
	#if NS_BLOCKS_AVAILABLE
380
	[self releaseBlocksOnMainThread];
381
	#endif
382

    
383
	[super dealloc];
384
}
385

    
386
#if NS_BLOCKS_AVAILABLE
387
- (void)releaseBlocksOnMainThread
388
{
389
	NSMutableArray *blocks = [NSMutableArray array];
390
	if (completionBlock) {
391
		[blocks addObject:completionBlock];
392
		[completionBlock release];
393
		completionBlock = nil;
394
	}
395
	if (failureBlock) {
396
		[blocks addObject:failureBlock];
397
		[failureBlock release];
398
		failureBlock = nil;
399
	}
400
	if (startedBlock) {
401
		[blocks addObject:startedBlock];
402
		[startedBlock release];
403
		startedBlock = nil;
404
	}
405
	if (headersReceivedBlock) {
406
		[blocks addObject:headersReceivedBlock];
407
		[headersReceivedBlock release];
408
		headersReceivedBlock = nil;
409
	}
410
	if (bytesReceivedBlock) {
411
		[blocks addObject:bytesReceivedBlock];
412
		[bytesReceivedBlock release];
413
		bytesReceivedBlock = nil;
414
	}
415
	if (bytesSentBlock) {
416
		[blocks addObject:bytesSentBlock];
417
		[bytesSentBlock release];
418
		bytesSentBlock = nil;
419
	}
420
	if (downloadSizeIncrementedBlock) {
421
		[blocks addObject:downloadSizeIncrementedBlock];
422
		[downloadSizeIncrementedBlock release];
423
		downloadSizeIncrementedBlock = nil;
424
	}
425
	if (uploadSizeIncrementedBlock) {
426
		[blocks addObject:uploadSizeIncrementedBlock];
427
		[uploadSizeIncrementedBlock release];
428
		uploadSizeIncrementedBlock = nil;
429
	}
430
	if (dataReceivedBlock) {
431
		[blocks addObject:dataReceivedBlock];
432
		[dataReceivedBlock release];
433
		dataReceivedBlock = nil;
434
	}
435
	if (proxyAuthenticationNeededBlock) {
436
		[blocks addObject:proxyAuthenticationNeededBlock];
437
		[proxyAuthenticationNeededBlock release];
438
		proxyAuthenticationNeededBlock = nil;
439
	}
440
	if (authenticationNeededBlock) {
441
		[blocks addObject:authenticationNeededBlock];
442
		[authenticationNeededBlock release];
443
		authenticationNeededBlock = nil;
444
	}
445
	[[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
446
}
447
// Always called on main thread
448
+ (void)releaseBlocks:(NSArray *)blocks
449
{
450
	// Blocks will be released when this method exits
451
}
452
#endif
453

    
454

    
455
#pragma mark setup request
456

    
457
- (void)addRequestHeader:(NSString *)header value:(NSString *)value
458
{
459
	if (!requestHeaders) {
460
		[self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]];
461
	}
462
	[requestHeaders setObject:value forKey:header];
463
}
464

    
465
// This function will be called either just before a request starts, or when postLength is needed, whichever comes first
466
// postLength must be set by the time this function is complete
467
- (void)buildPostBody
468
{
469

    
470
	if ([self haveBuiltPostBody]) {
471
		return;
472
	}
473
	
474
	// Are we submitting the request body from a file on disk
475
	if ([self postBodyFilePath]) {
476
		
477
		// If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream
478
		if ([self postBodyWriteStream]) {
479
			[[self postBodyWriteStream] close];
480
			[self setPostBodyWriteStream:nil];
481
		}
482

    
483
		
484
		NSString *path;
485
		if ([self shouldCompressRequestBody]) {
486
			if (![self compressedPostBodyFilePath]) {
487
				[self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
488
				
489
				NSError *err = nil;
490
				if (![ASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) {
491
					[self failWithError:err];
492
					return;
493
				}
494
			}
495
			path = [self compressedPostBodyFilePath];
496
		} else {
497
			path = [self postBodyFilePath];
498
		}
499
		NSError *err = nil;
500
		[self setPostLength:[[[NSFileManager defaultManager] attributesOfItemAtPath:path error:&err] fileSize]];
501
		if (err) {
502
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '@%'",path],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
503
			return;
504
		}
505
		
506
	// Otherwise, we have an in-memory request body
507
	} else {
508
		if ([self shouldCompressRequestBody]) {
509
			NSError *err = nil;
510
			NSData *compressedBody = [ASIDataCompressor compressData:[self postBody] error:&err];
511
			if (err) {
512
				[self failWithError:err];
513
				return;
514
			}
515
			[self setCompressedPostBody:compressedBody];
516
			[self setPostLength:[[self compressedPostBody] length]];
517
		} else {
518
			[self setPostLength:[[self postBody] length]];
519
		}
520
	}
521
		
522
	if ([self postLength] > 0) {
523
		if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) {
524
			[self setRequestMethod:@"POST"];
525
		}
526
		[self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]];
527
	}
528
	[self setHaveBuiltPostBody:YES];
529

    
530
}
531

    
532
// Sets up storage for the post body
533
- (void)setupPostBody
534
{
535
	if ([self shouldStreamPostDataFromDisk]) {
536
		if (![self postBodyFilePath]) {
537
			[self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
538
			[self setDidCreateTemporaryPostDataFile:YES];
539
		}
540
		if (![self postBodyWriteStream]) {
541
			[self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]];
542
			[[self postBodyWriteStream] open];
543
		}
544
	} else {
545
		if (![self postBody]) {
546
			[self setPostBody:[[[NSMutableData alloc] init] autorelease]];
547
		}
548
	}	
549
}
550

    
551
- (void)appendPostData:(NSData *)data
552
{
553
	[self setupPostBody];
554
	if ([data length] == 0) {
555
		return;
556
	}
557
	if ([self shouldStreamPostDataFromDisk]) {
558
		[[self postBodyWriteStream] write:[data bytes] maxLength:[data length]];
559
	} else {
560
		[[self postBody] appendData:data];
561
	}
562
}
563

    
564
- (void)appendPostDataFromFile:(NSString *)file
565
{
566
	[self setupPostBody];
567
	NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease];
568
	[stream open];
569
	NSUInteger bytesRead;
570
	while ([stream hasBytesAvailable]) {
571
		
572
		unsigned char buffer[1024*256];
573
		bytesRead = [stream read:buffer maxLength:sizeof(buffer)];
574
		if (bytesRead == 0) {
575
			break;
576
		}
577
		if ([self shouldStreamPostDataFromDisk]) {
578
			[[self postBodyWriteStream] write:buffer maxLength:bytesRead];
579
		} else {
580
			[[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]];
581
		}
582
	}
583
	[stream close];
584
}
585

    
586
- (NSURL *)url
587
{
588
	[[self cancelledLock] lock];
589
	NSURL *u = url;
590
	[[self cancelledLock] unlock];
591
	return u;
592
}
593

    
594

    
595
- (void)setURL:(NSURL *)newURL
596
{
597
	[[self cancelledLock] lock];
598
	if ([newURL isEqual:[self url]]) {
599
		[[self cancelledLock] unlock];
600
		return;
601
	}
602
	[url release];
603
	url = [newURL retain];
604
	if (requestAuthentication) {
605
		CFRelease(requestAuthentication);
606
		requestAuthentication = NULL;
607
	}
608
	if (proxyAuthentication) {
609
		CFRelease(proxyAuthentication);
610
		proxyAuthentication = NULL;
611
	}
612
	if (request) {
613
		CFRelease(request);
614
		request = NULL;
615
	}
616
	[self setRedirectURL:nil];
617
	[[self cancelledLock] unlock];
618
}
619

    
620
- (id)delegate
621
{
622
	[[self cancelledLock] lock];
623
	id d = delegate;
624
	[[self cancelledLock] unlock];
625
	return d;
626
}
627

    
628
- (void)setDelegate:(id)newDelegate
629
{
630
	[[self cancelledLock] lock];
631
	delegate = newDelegate;
632
	[[self cancelledLock] unlock];
633
}
634

    
635
- (id)queue
636
{
637
	[[self cancelledLock] lock];
638
	id q = queue;
639
	[[self cancelledLock] unlock];
640
	return q;
641
}
642

    
643

    
644
- (void)setQueue:(id)newQueue
645
{
646
	[[self cancelledLock] lock];
647
	if (newQueue != queue) {
648
		[queue release];
649
		queue = [newQueue retain];
650
	}
651
	[[self cancelledLock] unlock];
652
}
653

    
654
#pragma mark get information about this request
655

    
656
// cancel the request - this must be run on the same thread as the request is running on
657
- (void)cancelOnRequestThread
658
{
659
	#if DEBUG_REQUEST_STATUS
660
	NSLog(@"Request cancelled: %@",self);
661
	#endif
662
    
663
	[[self cancelledLock] lock];
664

    
665
    if ([self isCancelled] || [self complete]) {
666
		[[self cancelledLock] unlock];
667
		return;
668
	}
669
	[self failWithError:ASIRequestCancelledError];
670
	[self setComplete:YES];
671
	[self cancelLoad];
672
	
673
	CFRetain(self);
674
    [self willChangeValueForKey:@"isCancelled"];
675
    cancelled = YES;
676
    [self didChangeValueForKey:@"isCancelled"];
677
    
678
	[[self cancelledLock] unlock];
679
	CFRelease(self);
680
}
681

    
682
- (void)cancel
683
{
684
    [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];    
685
}
686

    
687
- (void)clearDelegatesAndCancel
688
{
689
	[[self cancelledLock] lock];
690

    
691
	// Clear delegates
692
	[self setDelegate:nil];
693
	[self setQueue:nil];
694
	[self setDownloadProgressDelegate:nil];
695
	[self setUploadProgressDelegate:nil];
696

    
697
	#if NS_BLOCKS_AVAILABLE
698
	// Clear blocks
699
	[self releaseBlocksOnMainThread];
700
	#endif
701

    
702
	[[self cancelledLock] unlock];
703
	[self cancel];
704
}
705

    
706

    
707
- (BOOL)isCancelled
708
{
709
    BOOL result;
710
    
711
	[[self cancelledLock] lock];
712
    result = cancelled;
713
    [[self cancelledLock] unlock];
714
    
715
    return result;
716
}
717

    
718
// Call this method to get the received data as an NSString. Don't use for binary data!
719
- (NSString *)responseString
720
{
721
	NSData *data = [self responseData];
722
	if (!data) {
723
		return nil;
724
	}
725
	
726
	return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
727
}
728

    
729
- (BOOL)isResponseCompressed
730
{
731
	NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
732
	return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
733
}
734

    
735
- (NSData *)responseData
736
{	
737
	if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) {
738
		return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL];
739
	} else {
740
		return [self rawResponseData];
741
	}
742
	return nil;
743
}
744

    
745
#pragma mark running a request
746

    
747
- (void)startSynchronous
748
{
749
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
750
	NSLog(@"Starting synchronous request %@",self);
751
#endif
752
	[self setRunLoopMode:ASIHTTPRequestRunLoopMode];
753
	[self setInProgress:YES];
754

    
755
	if (![self isCancelled] && ![self complete]) {
756
		[self main];
757
		while (!complete) {
758
			[[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]];
759
		}
760
	}
761

    
762
	[self setInProgress:NO];
763
}
764

    
765
- (void)start
766
{
767
	[self setInProgress:YES];
768
	[self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
769
}
770

    
771
- (void)startAsynchronous
772
{
773
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
774
	NSLog(@"Starting asynchronous request %@",self);
775
#endif
776
	[sharedQueue addOperation:self];
777
}
778

    
779
#pragma mark concurrency
780

    
781
- (BOOL)isConcurrent
782
{
783
    return YES;
784
}
785

    
786
- (BOOL)isFinished 
787
{
788
	return finished;
789
}
790

    
791
- (BOOL)isExecuting {
792
	return [self inProgress];
793
}
794

    
795
#pragma mark request logic
796

    
797
// Create the request
798
- (void)main
799
{
800
	@try {
801
		
802
		[[self cancelledLock] lock];
803
		
804
		#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
805
		if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
806
			backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
807
				// Synchronize the cleanup call on the main thread in case
808
				// the task actually finishes at around the same time.
809
				dispatch_async(dispatch_get_main_queue(), ^{
810
					if (backgroundTask != UIBackgroundTaskInvalid)
811
					{
812
						[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
813
						backgroundTask = UIBackgroundTaskInvalid;
814
						[self cancel];
815
					}
816
				});
817
			}];
818
		}
819
		#endif
820

    
821

    
822
		// A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed.
823
		if ([self error]) {
824
			[self setComplete:YES];
825
			[self markAsFinished];
826
			return;		
827
		}
828

    
829
		[self setComplete:NO];
830
		[self setDidUseCachedResponse:NO];
831
		
832
		if (![self url]) {
833
			[self failWithError:ASIUnableToCreateRequestError];
834
			return;		
835
		}
836
		
837
		// Must call before we create the request so that the request method can be set if needs be
838
		if (![self mainRequest]) {
839
			[self buildPostBody];
840
		}
841
		
842
		if (![[self requestMethod] isEqualToString:@"GET"]) {
843
			[self setDownloadCache:nil];
844
		}
845
		
846
		
847
		// If we're redirecting, we'll already have a CFHTTPMessageRef
848
		if (request) {
849
			CFRelease(request);
850
		}
851

    
852
		// Create a new HTTP request.
853
		request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
854
		if (!request) {
855
			[self failWithError:ASIUnableToCreateRequestError];
856
			return;
857
		}
858

    
859
		//If this is a HEAD request generated by an ASINetworkQueue, we need to let the main request generate its headers first so we can use them
860
		if ([self mainRequest]) {
861
			[[self mainRequest] buildRequestHeaders];
862
		}
863
		
864
		// Even if this is a HEAD request with a mainRequest, we still need to call to give subclasses a chance to add their own to HEAD requests (ASIS3Request does this)
865
		[self buildRequestHeaders];
866
		
867
		if ([self downloadCache]) {
868

    
869
			// If this request should use the default policy, set its policy to the download cache's default policy
870
			if (![self cachePolicy]) {
871
				[self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
872
			}
873

    
874
			// If have have cached data that is valid for this request, use that and stop
875
			if ([[self downloadCache] canUseCachedDataForRequest:self]) {
876
				[self useDataFromCache];
877
				return;
878
			}
879

    
880
			// If cached data is stale, or we have been told to ask the server if it has been modified anyway, we need to add headers for a conditional GET
881
			if ([self cachePolicy] & (ASIAskServerIfModifiedWhenStaleCachePolicy|ASIAskServerIfModifiedCachePolicy)) {
882

    
883
				NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
884
				if (cachedHeaders) {
885
					NSString *etag = [cachedHeaders objectForKey:@"Etag"];
886
					if (etag) {
887
						[[self requestHeaders] setObject:etag forKey:@"If-None-Match"];
888
					}
889
					NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"];
890
					if (lastModified) {
891
						[[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"];
892
					}
893
				}
894
			}
895
		}
896

    
897
		[self applyAuthorizationHeader];
898
		
899
		
900
		NSString *header;
901
		for (header in [self requestHeaders]) {
902
			CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]);
903
		}
904
			
905
		[self startRequest];
906
		
907
	} @catch (NSException *exception) {
908
		NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]];
909
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]];
910

    
911
	} @finally {
912
		[[self cancelledLock] unlock];
913
	}
914
}
915

    
916
- (void)applyAuthorizationHeader
917
{
918
	// Do we want to send credentials before we are asked for them?
919
	if (![self shouldPresentCredentialsBeforeChallenge]) {
920
		return;
921
	}
922
		
923
	// First, see if we have any credentials we can use in the session store
924
	NSDictionary *credentials = nil;
925
	if ([self useSessionPersistence]) {
926
		credentials = [self findSessionAuthenticationCredentials];
927
	}
928
	
929
	
930
	// Are any credentials set on this request that might be used for basic authentication?
931
	if ([self username] && [self password] && ![self domain]) {
932
		
933
		// If we have stored credentials, is this server asking for basic authentication? If we don't have credentials, we'll assume basic
934
		if (!credentials || (CFStringRef)[credentials objectForKey:@"AuthenticationScheme"] == kCFHTTPAuthenticationSchemeBasic) {
935
			[self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
936
		}
937
	}
938
	
939
	if (credentials && ![[self requestHeaders] objectForKey:@"Authorization"]) {
940
		
941
		// When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
942
		// (credentials for Digest and NTLM will always be stored like this)
943
		if ([credentials objectForKey:@"Authentication"]) {
944
			
945
			// If we've already talked to this server and have valid credentials, let's apply them to the request
946
			if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
947
				[[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
948
			}
949
			
950
			// If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
951
			// When this happens, we'll need to create the Authorization header ourselves
952
		} else {
953
			NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
954
			[self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
955
		}
956
	}
957
	if ([self useSessionPersistence]) {
958
		credentials = [self findSessionProxyAuthenticationCredentials];
959
		if (credentials) {
960
			if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
961
				[[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
962
			}
963
		}
964
	}
965
}
966

    
967
- (void)applyCookieHeader
968
{
969
	// Add cookies from the persistent (mac os global) store
970
	if ([self useCookiePersistence]) {
971
		NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[self url] absoluteURL]];
972
		if (cookies) {
973
			[[self requestCookies] addObjectsFromArray:cookies];
974
		}
975
	}
976
	
977
	// Apply request cookies
978
	NSArray *cookies;
979
	if ([self mainRequest]) {
980
		cookies = [[self mainRequest] requestCookies];
981
	} else {
982
		cookies = [self requestCookies];
983
	}
984
	if ([cookies count] > 0) {
985
		NSHTTPCookie *cookie;
986
		NSString *cookieHeader = nil;
987
		for (cookie in cookies) {
988
			if (!cookieHeader) {
989
				cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]];
990
			} else {
991
				cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]];
992
			}
993
		}
994
		if (cookieHeader) {
995
			[self addRequestHeader:@"Cookie" value:cookieHeader];
996
		}
997
	}	
998
}
999

    
1000
- (void)buildRequestHeaders
1001
{
1002
	if ([self haveBuiltRequestHeaders]) {
1003
		return;
1004
	}
1005
	[self setHaveBuiltRequestHeaders:YES];
1006
	
1007
	if ([self mainRequest]) {
1008
		for (NSString *header in [[self mainRequest] requestHeaders]) {
1009
			[self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]];
1010
		}
1011
		return;
1012
	}
1013
	
1014
	[self applyCookieHeader];
1015
	
1016
	// Build and set the user agent string if the request does not already have a custom user agent specified
1017
	if (![[self requestHeaders] objectForKey:@"User-Agent"]) {
1018
		NSString *userAgentString = [ASIHTTPRequest defaultUserAgentString];
1019
		if (userAgentString) {
1020
			[self addRequestHeader:@"User-Agent" value:userAgentString];
1021
		}
1022
	}
1023
	
1024
	
1025
	// Accept a compressed response
1026
	if ([self allowCompressedResponse]) {
1027
		[self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
1028
	}
1029
	
1030
	// Configure a compressed request body
1031
	if ([self shouldCompressRequestBody]) {
1032
		[self addRequestHeader:@"Content-Encoding" value:@"gzip"];
1033
	}
1034
	
1035
	// Should this request resume an existing download?
1036
	[self updatePartialDownloadSize];
1037
	if ([self partialDownloadSize]) {
1038
		[self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]];
1039
	}
1040
}
1041

    
1042
- (void)updatePartialDownloadSize
1043
{
1044
	if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [[NSFileManager defaultManager] fileExistsAtPath:[self temporaryFileDownloadPath]]) {
1045
		NSError *err = nil;
1046
		[self setPartialDownloadSize:[[[NSFileManager defaultManager] attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
1047
		if (err) {
1048
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '@%'",[self temporaryFileDownloadPath]],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]];
1049
			return;
1050
		}
1051
	}
1052
}
1053

    
1054
- (void)startRequest
1055
{
1056
	if ([self isCancelled]) {
1057
		return;
1058
	}
1059
	
1060
	[self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]];
1061
	
1062
	[self setDownloadComplete:NO];
1063
	[self setComplete:NO];
1064
	[self setTotalBytesRead:0];
1065
	[self setLastBytesRead:0];
1066
	
1067
	if ([self redirectCount] == 0) {
1068
		[self setOriginalURL:[self url]];
1069
	}
1070
	
1071
	// If we're retrying a request, let's remove any progress we made
1072
	if ([self lastBytesSent] > 0) {
1073
		[self removeUploadProgressSoFar];
1074
	}
1075
	
1076
	[self setLastBytesSent:0];
1077
	[self setContentLength:0];
1078
	[self setResponseHeaders:nil];
1079
	if (![self downloadDestinationPath]) {
1080
		[self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
1081
    }
1082
	
1083
	
1084
    //
1085
	// Create the stream for the request
1086
	//
1087
	
1088
	[self setReadStreamIsScheduled:NO];
1089
	
1090
	// Do we need to stream the request body from disk
1091
	if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self postBodyFilePath]]) {
1092
		
1093
		// Are we gzipping the request body?
1094
		if ([self compressedPostBodyFilePath] && [[NSFileManager defaultManager] fileExistsAtPath:[self compressedPostBodyFilePath]]) {
1095
			[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
1096
		} else {
1097
			[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
1098
		}
1099
		[self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]];
1100
    } else {
1101
		
1102
		// If we have a request body, we'll stream it from memory using our custom stream, so that we can measure bandwidth use and it can be bandwidth-throttled if necessary
1103
		if ([self postBody] && [[self postBody] length] > 0) {
1104
			if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
1105
				[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
1106
			} else if ([self postBody]) {
1107
				[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
1108
			}
1109
			[self setReadStream:[(NSInputStream *)CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream]) autorelease]];
1110
		
1111
		} else {
1112
			[self setReadStream:[(NSInputStream *)CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request) autorelease]];
1113
		}
1114
	}
1115

    
1116
	if (![self readStream]) {
1117
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
1118
        return;
1119
    }
1120

    
1121

    
1122
    
1123
    
1124
    //
1125
    // Handle SSL certificate settings
1126
    //
1127

    
1128
    if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
1129

    
1130
        NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
1131

    
1132
        // Tell CFNetwork not to validate SSL certificates
1133
        if (![self validatesSecureCertificate]) {
1134
            [sslProperties setObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
1135
        }
1136

    
1137
        // Tell CFNetwork to use a client certificate
1138
        if (clientCertificateIdentity) {
1139

    
1140
			NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
1141

    
1142
			// The first object in the array is our SecIdentityRef
1143
			[certificates addObject:(id)clientCertificateIdentity];
1144

    
1145
			// If we've added any additional certificates, add them too
1146
			for (id cert in clientCertificates) {
1147
				[certificates addObject:cert];
1148
			}
1149
            [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
1150
        }
1151

    
1152
        CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
1153
    }
1154

    
1155
    
1156
	
1157
	//
1158
	// Handle proxy settings
1159
	//
1160
	
1161
	// Have details of the proxy been set on this request
1162
	if (![self proxyHost] && ![self proxyPort]) {
1163
		
1164
		// If not, we need to figure out what they'll be
1165
		
1166
		NSArray *proxies = nil;
1167
		
1168
		// Have we been given a proxy auto config file?
1169
		if ([self PACurl]) {
1170
			
1171
			proxies = [ASIHTTPRequest proxiesForURL:[self url] fromPAC:[self PACurl]];
1172
			
1173
			// Detect proxy settings and apply them	
1174
		} else {
1175
			
1176
#if TARGET_OS_IPHONE
1177
			NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]);
1178
#else
1179
			NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)SCDynamicStoreCopyProxies(NULL) autorelease]);
1180
#endif
1181
			
1182
			proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings) autorelease]);
1183
			
1184
			// Now check to see if the proxy settings contained a PAC url, we need to run the script to get the real list of proxies if so
1185
			NSDictionary *settings = [proxies objectAtIndex:0];
1186
			if ([settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]) {
1187
				proxies = [ASIHTTPRequest proxiesForURL:[self url] fromPAC:[settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]];
1188
			}
1189
		}
1190
		
1191
		if (!proxies) {
1192
			[self setReadStream:nil];
1193
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to obtain information on proxy servers needed for request",NSLocalizedDescriptionKey,nil]]];
1194
			return;			
1195
		}
1196
		// I don't really understand why the dictionary returned by CFNetworkCopyProxiesForURL uses different key names from CFNetworkCopySystemProxySettings/SCDynamicStoreCopyProxies
1197
		// and why its key names are documented while those we actually need to use don't seem to be (passing the kCF* keys doesn't seem to work)
1198
		if ([proxies count] > 0) {
1199
			NSDictionary *settings = [proxies objectAtIndex:0];
1200
			[self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]];
1201
			[self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]];
1202
			[self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]];
1203
		}
1204
	}
1205
	if ([self proxyHost] && [self proxyPort]) {
1206
		NSString *hostKey;
1207
		NSString *portKey;
1208

    
1209
		if (![self proxyType]) {
1210
			[self setProxyType:(NSString *)kCFProxyTypeHTTP];
1211
		}
1212

    
1213
		if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
1214
			hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
1215
			portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
1216
		} else {
1217
			hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
1218
			portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
1219
			if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
1220
				hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
1221
				portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
1222
			}
1223
		}
1224
		NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil];
1225

    
1226
		if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
1227
			CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse);
1228
		} else {
1229
			CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse);
1230
		}
1231
	}
1232

    
1233
	//
1234
	// Handle persistent connections
1235
	//
1236
	
1237
	[ASIHTTPRequest expirePersistentConnections];
1238

    
1239
	[connectionsLock lock];
1240
	
1241
	
1242
	if (![[self url] host] || ![[self url] scheme]) {
1243
		[self setConnectionInfo:nil];
1244
		[self setShouldAttemptPersistentConnection:NO];
1245
	}
1246
	
1247
	// Will store the old stream that was using this connection (if there was one) so we can clean it up once we've opened our own stream
1248
	NSInputStream *oldStream = nil;
1249
	
1250
	// Use a persistent connection if possible
1251
	if ([self shouldAttemptPersistentConnection]) {
1252
		
1253

    
1254
		// If we are redirecting, we will re-use the current connection only if we are connecting to the same server
1255
		if ([self connectionInfo]) {
1256
			
1257
			if (![[[self connectionInfo] objectForKey:@"host"] isEqualToString:[[self url] host]] || ![[[self connectionInfo] objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] || [(NSNumber *)[[self connectionInfo] objectForKey:@"port"] intValue] != [[[self url] port] intValue]) {
1258
				[self setConnectionInfo:nil];
1259
				
1260
			// Check if we should have expired this connection
1261
			} else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) {
1262
				#if DEBUG_PERSISTENT_CONNECTIONS
1263
				NSLog(@"Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]);
1264
				#endif
1265
				[persistentConnectionsPool removeObject:[self connectionInfo]];
1266
				[self setConnectionInfo:nil];
1267
			}
1268
		}
1269
		
1270
		
1271
		
1272
		if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
1273
			
1274
			// Look for a connection to the same server in the pool
1275
			for (NSMutableDictionary *existingConnection in persistentConnectionsPool) {
1276
				if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"host"] isEqualToString:[[self url] host]] && [[existingConnection objectForKey:@"scheme"] isEqualToString:[[self url] scheme]] && [(NSNumber *)[existingConnection objectForKey:@"port"] intValue] == [[[self url] port] intValue]) {
1277
					[self setConnectionInfo:existingConnection];
1278
				}
1279
			}
1280
		}
1281
		
1282
		if ([[self connectionInfo] objectForKey:@"stream"]) {
1283
			oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain];
1284

    
1285
		}
1286
		
1287
		// No free connection was found in the pool matching the server/scheme/port we're connecting to, we'll need to create a new one
1288
		if (![self connectionInfo]) {
1289
			[self setConnectionInfo:[NSMutableDictionary dictionary]];
1290
			nextConnectionNumberToCreate++;
1291
			[[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
1292
			[[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
1293
			[[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
1294
			[[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
1295
			[persistentConnectionsPool addObject:[self connectionInfo]];
1296
		}
1297
		
1298
		// If we are retrying this request, it will already have a requestID
1299
		if (![self requestID]) {
1300
			nextRequestID++;
1301
			[self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]];
1302
		}
1303
		[[self connectionInfo] setObject:[self requestID] forKey:@"request"];		
1304
		[[self connectionInfo] setObject:[self readStream] forKey:@"stream"];
1305
		CFReadStreamSetProperty((CFReadStreamRef)[self readStream],  kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
1306
		
1307
		#if DEBUG_PERSISTENT_CONNECTIONS
1308
		NSLog(@"Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]);
1309
		#endif
1310
		
1311
		
1312
		// Tag the stream with an id that tells it which connection to use behind the scenes
1313
		// See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
1314
		
1315
		CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
1316
	
1317
	}
1318
	
1319
	[connectionsLock unlock];
1320

    
1321
	// Schedule the stream
1322
	if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) {
1323
		[self scheduleReadStream];
1324
	}
1325
	
1326
	BOOL streamSuccessfullyOpened = NO;
1327

    
1328

    
1329
   // Start the HTTP connection
1330
	CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
1331
    if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
1332
		if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
1333
			streamSuccessfullyOpened = YES;
1334
		}
1335
	}
1336
	
1337
	// Here, we'll close the stream that was previously using this connection, if there was one
1338
	// We've kept it open until now (when we've just opened a new stream) so that the new stream can make use of the old connection
1339
	// http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html
1340
	if (oldStream) {
1341
		[oldStream close];
1342
		[oldStream release];
1343
		oldStream = nil;
1344
	}
1345

    
1346
	if (!streamSuccessfullyOpened) {
1347
		[self setConnectionCanBeReused:NO];
1348
		[self destroyReadStream];
1349
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
1350
		return;	
1351
	}
1352
	
1353
	if (![self mainRequest]) {
1354
		if ([self shouldResetUploadProgress]) {
1355
			if ([self showAccurateProgress]) {
1356
				[self incrementUploadSizeBy:[self postLength]];
1357
			} else {
1358
				[self incrementUploadSizeBy:1];	 
1359
			}
1360
			[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1];
1361
		}
1362
		if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) {
1363
			[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1];
1364
		}
1365
	}	
1366
	
1367
	
1368
	// Record when the request started, so we can timeout if nothing happens
1369
	[self setLastActivityTime:[NSDate date]];
1370
	[self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]];
1371
	[[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]];
1372
}
1373

    
1374
- (void)setStatusTimer:(NSTimer *)timer
1375
{
1376
	CFRetain(self);
1377
	// We must invalidate the old timer here, not before we've created and scheduled a new timer
1378
	// This is because the timer may be the only thing retaining an asynchronous request
1379
	if (statusTimer && timer != statusTimer) {
1380
		[statusTimer invalidate];
1381
		[statusTimer release];
1382
	}
1383
	statusTimer = [timer retain];
1384
	CFRelease(self);
1385
}
1386

    
1387
// This gets fired every 1/4 of a second to update the progress and work out if we need to timeout
1388
- (void)updateStatus:(NSTimer*)timer
1389
{
1390
	[self checkRequestStatus];
1391
	if (![self inProgress]) {
1392
		[self setStatusTimer:nil];
1393
	}
1394
}
1395

    
1396
- (void)performRedirect
1397
{
1398
	[self setURL:[self redirectURL]];
1399
	[self setComplete:YES];
1400
	[self setNeedsRedirect:NO];
1401
	[self setRedirectCount:[self redirectCount]+1];
1402

    
1403
	if ([self redirectCount] > RedirectionLimit) {
1404
		// Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool.
1405
		[self failWithError:ASITooMuchRedirectionError];
1406
		[self setComplete:YES];
1407
	} else {
1408
		// Go all the way back to the beginning and build the request again, so that we can apply any new cookies
1409
		[self main];
1410
	}
1411
}
1412

    
1413
// Called by delegate to resume loading with a new url after the delegate received request:willRedirectToURL:
1414
- (void)redirectToURL:(NSURL *)newURL
1415
{
1416
	[self setRedirectURL:newURL];
1417
	[self performSelector:@selector(performRedirect) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
1418
}
1419

    
1420
- (BOOL)shouldTimeOut
1421
{
1422
	NSTimeInterval secondsSinceLastActivity = [[NSDate date] timeIntervalSinceDate:lastActivityTime];
1423
	// See if we need to timeout
1424
	if ([self readStream] && [self readStreamIsScheduled] && [self lastActivityTime] && [self timeOutSeconds] > 0 && secondsSinceLastActivity > [self timeOutSeconds]) {
1425
		
1426
		// We have no body, or we've sent more than the upload buffer size,so we can safely time out here
1427
		if ([self postLength] == 0 || ([self uploadBufferSize] > 0 && [self totalBytesSent] > [self uploadBufferSize])) {
1428
			return YES;
1429
			
1430
		// ***Black magic warning***
1431
		// We have a body, but we've taken longer than timeOutSeconds to upload the first small chunk of data
1432
		// Since there's no reliable way to track upload progress for the first 32KB (iPhone) or 128KB (Mac) with CFNetwork, we'll be slightly more forgiving on the timeout, as there's a strong chance our connection is just very slow.
1433
		} else if (secondsSinceLastActivity > [self timeOutSeconds]*1.5) {
1434
			return YES;
1435
		}
1436
	}
1437
	return NO;
1438
}
1439

    
1440
- (void)checkRequestStatus
1441
{
1442
	// We won't let the request cancel while we're updating progress / checking for a timeout
1443
	[[self cancelledLock] lock];
1444
	// See if our NSOperationQueue told us to cancel
1445
	if ([self isCancelled] || [self complete]) {
1446
		[[self cancelledLock] unlock];
1447
		return;
1448
	}
1449
	
1450
	[self performThrottling];
1451
	
1452
	if ([self shouldTimeOut]) {			
1453
		// Do we need to auto-retry this request?
1454
		if ([self numberOfTimesToRetryOnTimeout] > [self retryCount]) {
1455

    
1456
			// If we are resuming a download, we may need to update the Range header to take account of data we've just downloaded
1457
			[self updatePartialDownloadSize];
1458
			if ([self partialDownloadSize]) {
1459
				CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)@"Range", (CFStringRef)[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]);
1460
			}
1461
			[self setRetryCount:[self retryCount]+1];
1462
			[self unscheduleReadStream];
1463
			[[self cancelledLock] unlock];
1464
			[self startRequest];
1465
			return;
1466
		}
1467
		[self failWithError:ASIRequestTimedOutError];
1468
		[self cancelLoad];
1469
		[self setComplete:YES];
1470
		[[self cancelledLock] unlock];
1471
		return;
1472
	}
1473

    
1474
	// readStream will be null if we aren't currently running (perhaps we're waiting for a delegate to supply credentials)
1475
	if ([self readStream]) {
1476
		
1477
		// If we have a post body
1478
		if ([self postLength]) {
1479
		
1480
			[self setLastBytesSent:totalBytesSent];	
1481
			
1482
			// Find out how much data we've uploaded so far
1483
			[self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]];
1484
			if (totalBytesSent > lastBytesSent) {
1485
				
1486
				// We've uploaded more data,  reset the timeout
1487
				[self setLastActivityTime:[NSDate date]];
1488
				[ASIHTTPRequest incrementBandwidthUsedInLastSecond:(unsigned long)(totalBytesSent-lastBytesSent)];		
1489
						
1490
				#if DEBUG_REQUEST_STATUS
1491
				if ([self totalBytesSent] == [self postLength]) {
1492
					NSLog(@"Request %@ finished uploading data",self);
1493
				}
1494
				#endif
1495
			}
1496
		}
1497
			
1498
		[self updateProgressIndicators];
1499

    
1500
	}
1501
	
1502
	[[self cancelledLock] unlock];
1503
}
1504

    
1505

    
1506
// Cancel loading and clean up. DO NOT USE THIS TO CANCEL REQUESTS - use [request cancel] instead
1507
- (void)cancelLoad
1508
{
1509
    [self destroyReadStream];
1510
	
1511
	[[self postBodyReadStream] close];
1512
	[self setPostBodyReadStream:nil];
1513
	
1514
    if ([self rawResponseData]) {
1515
		[self setRawResponseData:nil];
1516
	
1517
	// If we were downloading to a file
1518
	} else if ([self temporaryFileDownloadPath]) {
1519
		[[self fileDownloadOutputStream] close];
1520
		[self setFileDownloadOutputStream:nil];
1521
		
1522
		[[self inflatedFileDownloadOutputStream] close];
1523
		[self setInflatedFileDownloadOutputStream:nil];
1524
		
1525
		// If we haven't said we might want to resume, let's remove the temporary file too
1526
		if (![self allowResumeForFileDownloads]) {
1527
			[self removeTemporaryDownloadFile];
1528
		}
1529
		[self removeTemporaryUncompressedDownloadFile];
1530
	}
1531
	
1532
	// Clean up any temporary file used to store request body for streaming
1533
	if (![self authenticationNeeded] && [self didCreateTemporaryPostDataFile]) {
1534
		[self removeTemporaryUploadFile];
1535
		[self removeTemporaryCompressedUploadFile];
1536
		[self setDidCreateTemporaryPostDataFile:NO];
1537
	}
1538
	
1539
	[self setResponseHeaders:nil];
1540
}
1541

    
1542
#pragma mark HEAD request
1543

    
1544
// Used by ASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself)
1545
- (ASIHTTPRequest *)HEADRequest
1546
{
1547
	ASIHTTPRequest *headRequest = [[self class] requestWithURL:[self url]];
1548
	
1549
	// Copy the properties that make sense for a HEAD request
1550
	[headRequest setRequestHeaders:[[[self requestHeaders] mutableCopy] autorelease]];
1551
	[headRequest setRequestCookies:[[[self requestCookies] mutableCopy] autorelease]];
1552
	[headRequest setUseCookiePersistence:[self useCookiePersistence]];
1553
	[headRequest setUseKeychainPersistence:[self useKeychainPersistence]];
1554
	[headRequest setUseSessionPersistence:[self useSessionPersistence]];
1555
	[headRequest setAllowCompressedResponse:[self allowCompressedResponse]];
1556
	[headRequest setUsername:[self username]];
1557
	[headRequest setPassword:[self password]];
1558
	[headRequest setDomain:[self domain]];
1559
	[headRequest setProxyUsername:[self proxyUsername]];
1560
	[headRequest setProxyPassword:[self proxyPassword]];
1561
	[headRequest setProxyDomain:[self proxyDomain]];
1562
	[headRequest setProxyHost:[self proxyHost]];
1563
	[headRequest setProxyPort:[self proxyPort]];
1564
	[headRequest setProxyType:[self proxyType]];
1565
	[headRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]];
1566
	[headRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]];
1567
	[headRequest setTimeOutSeconds:[self timeOutSeconds]];
1568
	[headRequest setUseHTTPVersionOne:[self useHTTPVersionOne]];
1569
	[headRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
1570
    [headRequest setClientCertificateIdentity:clientCertificateIdentity];
1571
	[headRequest setClientCertificates:[[clientCertificates copy] autorelease]];
1572
	[headRequest setPACurl:[self PACurl]];
1573
	[headRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
1574
	[headRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]];
1575
	[headRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]];
1576
	[headRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]];
1577
	[headRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]];
1578
	
1579
	[headRequest setMainRequest:self];
1580
	[headRequest setRequestMethod:@"HEAD"];
1581
	return headRequest;
1582
}
1583

    
1584

    
1585
#pragma mark upload/download progress
1586

    
1587

    
1588
- (void)updateProgressIndicators
1589
{
1590
	//Only update progress if this isn't a HEAD request used to preset the content-length
1591
	if (![self mainRequest]) {
1592
		if ([self showAccurateProgress] || ([self complete] && ![self updatedProgress])) {
1593
			[self updateUploadProgress];
1594
			[self updateDownloadProgress];
1595
		}
1596
	}
1597
}
1598

    
1599
- (id)uploadProgressDelegate
1600
{
1601
	[[self cancelledLock] lock];
1602
	id d = [[uploadProgressDelegate retain] autorelease];
1603
	[[self cancelledLock] unlock];
1604
	return d;
1605
}
1606

    
1607
- (void)setUploadProgressDelegate:(id)newDelegate
1608
{
1609
	[[self cancelledLock] lock];
1610
	uploadProgressDelegate = newDelegate;
1611

    
1612
	#if !TARGET_OS_IPHONE
1613
	// If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView
1614
	double max = 1.0;
1615
	[ASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&uploadProgressDelegate withObject:nil amount:&max callerToRetain:nil];
1616
	#endif
1617
	[[self cancelledLock] unlock];
1618
}
1619

    
1620
- (id)downloadProgressDelegate
1621
{
1622
	[[self cancelledLock] lock];
1623
	id d = [[downloadProgressDelegate retain] autorelease];
1624
	[[self cancelledLock] unlock];
1625
	return d;
1626
}
1627

    
1628
- (void)setDownloadProgressDelegate:(id)newDelegate
1629
{
1630
	[[self cancelledLock] lock];
1631
	downloadProgressDelegate = newDelegate;
1632

    
1633
	#if !TARGET_OS_IPHONE
1634
	// If the downloadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView
1635
	double max = 1.0;
1636
	[ASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&downloadProgressDelegate withObject:nil amount:&max callerToRetain:nil];	
1637
	#endif
1638
	[[self cancelledLock] unlock];
1639
}
1640

    
1641

    
1642
- (void)updateDownloadProgress
1643
{
1644
	// We won't update download progress until we've examined the headers, since we might need to authenticate
1645
	if (![self responseHeaders] || [self needsRedirect] || !([self contentLength] || [self complete])) {
1646
		return;
1647
	}
1648
		
1649
	unsigned long long bytesReadSoFar = [self totalBytesRead]+[self partialDownloadSize];
1650
	unsigned long long value = 0;
1651
	
1652
	if ([self showAccurateProgress] && [self contentLength]) {
1653
		value = bytesReadSoFar-[self lastBytesRead];
1654
		if (value == 0) {
1655
			return;
1656
		}
1657
	} else {
1658
		value = 1;
1659
		[self setUpdatedProgress:YES];
1660
	}
1661
	if (!value) {
1662
		return;
1663
	}
1664

    
1665
	[ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self];
1666
	[ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&downloadProgressDelegate withObject:self amount:&value callerToRetain:self];
1667

    
1668
	[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self totalBytesRead]+[self partialDownloadSize] ofTotal:[self contentLength]+[self partialDownloadSize]];
1669

    
1670
	#if NS_BLOCKS_AVAILABLE
1671
    if (bytesReceivedBlock) {
1672
		unsigned long long totalSize = [self contentLength] + [self partialDownloadSize];
1673
		[self performBlockOnMainThread:^{ if (bytesReceivedBlock) { bytesReceivedBlock(value, totalSize); }}];
1674
    }
1675
	#endif
1676
	[self setLastBytesRead:bytesReadSoFar];
1677
}
1678

    
1679
- (void)updateUploadProgress
1680
{
1681
	if ([self isCancelled] || [self totalBytesSent] == 0) {
1682
		return;
1683
	}
1684
	
1685
	// If this is the first time we've written to the buffer, totalBytesSent will be the size of the buffer (currently seems to be 128KB on both Leopard and iPhone 2.2.1, 32KB on iPhone 3.0)
1686
	// If request body is less than the buffer size, totalBytesSent will be the total size of the request body
1687
	// We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written
1688
	if ([self uploadBufferSize] == 0 && [self totalBytesSent] != [self postLength]) {
1689
		[self setUploadBufferSize:[self totalBytesSent]];
1690
		[self incrementUploadSizeBy:-[self uploadBufferSize]];
1691
	}
1692
	
1693
	unsigned long long value = 0;
1694
	
1695
	if ([self showAccurateProgress]) {
1696
		if ([self totalBytesSent] == [self postLength] || [self lastBytesSent] > 0) {
1697
			value = [self totalBytesSent]-[self lastBytesSent];
1698
		} else {
1699
			return;
1700
		}
1701
	} else {
1702
		value = 1;
1703
		[self setUpdatedProgress:YES];
1704
	}
1705
	
1706
	if (!value) {
1707
		return;
1708
	}
1709
	
1710
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self];
1711
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&value callerToRetain:self];
1712
	[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self totalBytesSent]-[self uploadBufferSize] ofTotal:[self postLength]-[self uploadBufferSize]];
1713

    
1714
	#if NS_BLOCKS_AVAILABLE
1715
    if(bytesSentBlock){
1716
		unsigned long long totalSize = [self postLength];
1717
		[self performBlockOnMainThread:^{ if (bytesSentBlock) { bytesSentBlock(value, totalSize); }}];
1718
	}
1719
	#endif
1720
}
1721

    
1722

    
1723
- (void)incrementDownloadSizeBy:(long long)length
1724
{
1725
	[ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self];
1726
	[ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&downloadProgressDelegate withObject:self amount:&length callerToRetain:self];
1727

    
1728
	#if NS_BLOCKS_AVAILABLE
1729
    if(downloadSizeIncrementedBlock){
1730
		[self performBlockOnMainThread:^{ if (downloadSizeIncrementedBlock) { downloadSizeIncrementedBlock(length); }}];
1731
    }
1732
	#endif
1733
}
1734

    
1735
- (void)incrementUploadSizeBy:(long long)length
1736
{
1737
	[ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self];
1738
	[ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&uploadProgressDelegate withObject:self amount:&length callerToRetain:self];
1739

    
1740
	#if NS_BLOCKS_AVAILABLE
1741
    if(uploadSizeIncrementedBlock) {
1742
		[self performBlockOnMainThread:^{ if (uploadSizeIncrementedBlock) { uploadSizeIncrementedBlock(length); }}];
1743
    }
1744
	#endif
1745
}
1746

    
1747

    
1748
-(void)removeUploadProgressSoFar
1749
{
1750
	long long progressToRemove = -[self totalBytesSent];
1751
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&progressToRemove callerToRetain:self];
1752
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&progressToRemove callerToRetain:self];
1753
	[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:[self postLength]];
1754

    
1755
	#if NS_BLOCKS_AVAILABLE
1756
    if(bytesSentBlock){
1757
		unsigned long long totalSize = [self postLength];
1758
		[self performBlockOnMainThread:^{  if (bytesSentBlock) { bytesSentBlock(progressToRemove, totalSize); }}];
1759
	}
1760
	#endif
1761
}
1762

    
1763
#if NS_BLOCKS_AVAILABLE
1764
- (void)performBlockOnMainThread:(ASIBasicBlock)block
1765
{
1766
	[self performSelectorOnMainThread:@selector(callBlock:) withObject:[[block copy] autorelease] waitUntilDone:[NSThread isMainThread]];
1767
}
1768

    
1769
- (void)callBlock:(ASIBasicBlock)block
1770
{
1771
	block();
1772
}
1773
#endif
1774

    
1775

    
1776
+ (void)performSelector:(SEL)selector onTarget:(id *)target withObject:(id)object amount:(void *)amount callerToRetain:(id)callerToRetain
1777
{
1778
	if ([*target respondsToSelector:selector]) {
1779
		NSMethodSignature *signature = nil;
1780
		signature = [*target methodSignatureForSelector:selector];
1781
		NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
1782

    
1783
		[invocation setSelector:selector];
1784
		
1785
		int argumentNumber = 2;
1786
		
1787
		// If we got an object parameter, we pass a pointer to the object pointer
1788
		if (object) {
1789
			[invocation setArgument:&object atIndex:argumentNumber];
1790
			argumentNumber++;
1791
		}
1792
		
1793
		// For the amount we'll just pass the pointer directly so NSInvocation will call the method using the number itself rather than a pointer to it
1794
		if (amount) {
1795
			[invocation setArgument:amount atIndex:argumentNumber];
1796
		}
1797

    
1798
        SEL callback = @selector(performInvocation:onTarget:releasingObject:);
1799
        NSMethodSignature *cbSignature = [ASIHTTPRequest methodSignatureForSelector:callback];
1800
        NSInvocation *cbInvocation = [NSInvocation invocationWithMethodSignature:cbSignature];
1801
        [cbInvocation setSelector:callback];
1802
        [cbInvocation setTarget:self];
1803
        [cbInvocation setArgument:&invocation atIndex:2];
1804
        [cbInvocation setArgument:&target atIndex:3];
1805
		if (callerToRetain) {
1806
			[cbInvocation setArgument:&callerToRetain atIndex:4];
1807
		}
1808

    
1809
		CFRetain(invocation);
1810

    
1811
		// Used to pass in a request that we must retain until after the call
1812
		// We're using CFRetain rather than [callerToRetain retain] so things to avoid earthquakes when using garbage collection
1813
		if (callerToRetain) {
1814
			CFRetain(callerToRetain);
1815
		}
1816
        [cbInvocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:[NSThread isMainThread]];
1817
    }
1818
}
1819

    
1820
+ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease
1821
{
1822
    if (*target && [*target respondsToSelector:[invocation selector]]) {
1823
        [invocation invokeWithTarget:*target];
1824
    }
1825
	CFRelease(invocation);
1826
	if (objectToRelease) {
1827
		CFRelease(objectToRelease);
1828
	}
1829
}
1830
	
1831
	
1832
+ (void)updateProgressIndicator:(id *)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total
1833
{
1834
	#if TARGET_OS_IPHONE
1835
		// Cocoa Touch: UIProgressView
1836
		SEL selector = @selector(setProgress:);
1837
		float progressAmount = (float)((progress*1.0)/(total*1.0));
1838
		
1839
	#else
1840
		// Cocoa: NSProgressIndicator
1841
		double progressAmount = progressAmount = (progress*1.0)/(total*1.0);
1842
		SEL selector = @selector(setDoubleValue:);
1843
	#endif
1844
	
1845
	if (![*indicator respondsToSelector:selector]) {
1846
		return;
1847
	}
1848
	
1849
	[progressLock lock];
1850
	[ASIHTTPRequest performSelector:selector onTarget:indicator withObject:nil amount:&progressAmount callerToRetain:nil];
1851
	[progressLock unlock];
1852
}
1853

    
1854

    
1855
#pragma mark talking to delegates / calling blocks
1856

    
1857
/* ALWAYS CALLED ON MAIN THREAD! */
1858
- (void)requestStarted
1859
{
1860
	if ([self error] || [self mainRequest]) {
1861
		return;
1862
	}
1863
	if (delegate && [delegate respondsToSelector:didStartSelector]) {
1864
		[delegate performSelector:didStartSelector withObject:self];
1865
	}
1866
	if (queue && [queue respondsToSelector:@selector(requestStarted:)]) {
1867
		[queue performSelector:@selector(requestStarted:) withObject:self];
1868
	}
1869
	#if NS_BLOCKS_AVAILABLE
1870
	if(startedBlock){
1871
		startedBlock();
1872
	}
1873
	#endif
1874
}
1875

    
1876
/* ALWAYS CALLED ON MAIN THREAD! */
1877
- (void)requestRedirected
1878
{
1879
	if ([self error] || [self mainRequest]) {
1880
		return;
1881
	}
1882

    
1883
	if([[self delegate] respondsToSelector:@selector(requestRedirected:)]){
1884
		[[self delegate] performSelector:@selector(requestRedirected:) withObject:self];
1885
	}
1886
	#if NS_BLOCKS_AVAILABLE
1887
	if(requestRedirectedBlock){
1888
		requestRedirectedBlock();
1889
	}
1890
	#endif
1891
}
1892

    
1893

    
1894
/* ALWAYS CALLED ON MAIN THREAD! */
1895
- (void)requestReceivedResponseHeaders:(NSMutableDictionary *)newResponseHeaders
1896
{
1897
	if ([self error] || [self mainRequest]) {
1898
		return;
1899
	}
1900

    
1901
	if (delegate && [delegate respondsToSelector:didReceiveResponseHeadersSelector]) {
1902
		[delegate performSelector:didReceiveResponseHeadersSelector withObject:self withObject:newResponseHeaders];
1903
	}
1904
	if (queue && [queue respondsToSelector:@selector(request:didReceiveResponseHeaders:)]) {
1905
		[queue performSelector:@selector(request:didReceiveResponseHeaders:) withObject:self withObject:newResponseHeaders];
1906
	}
1907
    
1908
	#if NS_BLOCKS_AVAILABLE
1909
	if(headersReceivedBlock){
1910
		headersReceivedBlock(newResponseHeaders);
1911
    }
1912
	#endif
1913
}
1914

    
1915
/* ALWAYS CALLED ON MAIN THREAD! */
1916
- (void)requestWillRedirectToURL:(NSURL *)newURL
1917
{
1918
	if ([self error] || [self mainRequest]) {
1919
		return;
1920
	}
1921
	if (delegate && [delegate respondsToSelector:willRedirectSelector]) {
1922
		[delegate performSelector:willRedirectSelector withObject:self withObject:newURL];
1923
	}
1924
	if (queue && [queue respondsToSelector:@selector(request:willRedirectToURL:)]) {
1925
		[queue performSelector:@selector(request:willRedirectToURL:) withObject:self withObject:newURL];
1926
	}
1927
}
1928

    
1929
// added by mike mayo
1930
- (void)callRequestFinished {
1931
    [delegate performSelector:didFinishSelector withObject:self];    
1932
}
1933

    
1934
/* ALWAYS CALLED ON MAIN THREAD! */
1935
// Subclasses might override this method to process the result in the same thread
1936
// If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done
1937
- (void)requestFinished
1938
{
1939
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
1940
	NSLog(@"Request finished: %@",self);
1941
#endif
1942
    
1943
	if ([self error] || [self mainRequest]) {
1944
		return;
1945
	}
1946

    
1947
	if (delegate && [delegate respondsToSelector:didFinishSelector]) {
1948
        // replaced by mike mayo
1949
        // trying out not waiting until done, because NSNotificationCenter blocks the UI
1950
		//[delegate performSelector:didFinishSelector withObject:self];
1951
        [self performSelectorOnMainThread:@selector(callRequestFinished) withObject:nil waitUntilDone:NO];
1952
	}
1953
	if (queue && [queue respondsToSelector:@selector(requestFinished:)]) {
1954
		[queue performSelector:@selector(requestFinished:) withObject:self];
1955
	}
1956
	#if NS_BLOCKS_AVAILABLE
1957
	if(completionBlock){
1958
		completionBlock();
1959
	}
1960
	#endif
1961
}
1962

    
1963
/* ALWAYS CALLED ON MAIN THREAD! */
1964
- (void)reportFailure
1965
{
1966
	if (delegate && [delegate respondsToSelector:didFailSelector]) {
1967
		[delegate performSelector:didFailSelector withObject:self];
1968
	}
1969
	if (queue && [queue respondsToSelector:@selector(requestFailed:)]) {
1970
		[queue performSelector:@selector(requestFailed:) withObject:self];
1971
	}
1972
	#if NS_BLOCKS_AVAILABLE
1973
    if(failureBlock){
1974
        failureBlock();
1975
    }
1976
	#endif
1977
}
1978

    
1979
/* ALWAYS CALLED ON MAIN THREAD! */
1980
- (void)passOnReceivedData:(NSData *)data
1981
{
1982
	if (delegate && [delegate respondsToSelector:didReceiveDataSelector]) {
1983
		[delegate performSelector:didReceiveDataSelector withObject:self withObject:data];
1984
	}
1985

    
1986
	#if NS_BLOCKS_AVAILABLE
1987
	if (dataReceivedBlock) {
1988
		dataReceivedBlock(data);
1989
	}
1990
	#endif
1991
}
1992

    
1993
// Subclasses might override this method to perform error handling in the same thread
1994
// If you do this, don't forget to call [super failWithError:] to let the queue / delegate know we're done
1995
- (void)failWithError:(NSError *)theError
1996
{
1997
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
1998
	NSLog(@"Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed"));
1999
#endif
2000
	[self setComplete:YES];
2001
	
2002
	// Invalidate the current connection so subsequent requests don't attempt to reuse it
2003
	if (theError && [theError code] != ASIAuthenticationErrorType && [theError code] != ASITooMuchRedirectionErrorType) {
2004
		[connectionsLock lock];
2005
		#if DEBUG_PERSISTENT_CONNECTIONS
2006
		NSLog(@"Request #%@ failed and will invalidate connection #%@",[self requestID],[[self connectionInfo] objectForKey:@"id"]);
2007
		#endif
2008
		[[self connectionInfo] removeObjectForKey:@"request"];
2009
		[persistentConnectionsPool removeObject:[self connectionInfo]];
2010
		[connectionsLock unlock];
2011
		[self destroyReadStream];
2012
	}
2013
	if ([self connectionCanBeReused]) {
2014
		[[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"];
2015
	}
2016
	
2017
    if ([self isCancelled] || [self error]) {
2018
		return;
2019
	}
2020
	
2021
	if ([self downloadCache] && ([self cachePolicy] & ASIFallbackToCacheIfLoadFailsCachePolicy)) {
2022
		[self useDataFromCache];
2023
		return;
2024
	}
2025
	
2026
	
2027
	[self setError:theError];
2028
	
2029
	ASIHTTPRequest *failedRequest = self;
2030
	
2031
	// If this is a HEAD request created by an ASINetworkQueue or compatible queue delegate, make the main request fail
2032
	if ([self mainRequest]) {
2033
		failedRequest = [self mainRequest];
2034
		[failedRequest setError:theError];
2035
	}
2036

    
2037
	[failedRequest performSelectorOnMainThread:@selector(reportFailure) withObject:nil waitUntilDone:[NSThread isMainThread]];
2038
	
2039
    if (!inProgress)
2040
    {
2041
        // if we're not in progress, we can't notify the queue we've finished (doing so can cause a crash later on)
2042
        // "markAsFinished" will be at the start of main() when we are started
2043
        return;
2044
    }
2045
	[self markAsFinished];
2046
}
2047

    
2048
#pragma mark parsing HTTP response headers
2049

    
2050
- (void)readResponseHeaders
2051
{
2052
	[self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
2053

    
2054
	CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPResponseHeader);
2055
	if (!message) {
2056
		return;
2057
	}
2058
	
2059
	// Make sure we've received all the headers
2060
	if (!CFHTTPMessageIsHeaderComplete(message)) {
2061
		CFRelease(message);
2062
		return;
2063
	}
2064

    
2065
	#if DEBUG_REQUEST_STATUS
2066
	if ([self totalBytesSent] == [self postLength]) {
2067
		NSLog(@"Request %@ received response headers",self);
2068
	}
2069
	#endif		
2070

    
2071
	CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(message);
2072
	[self setResponseHeaders:(NSDictionary *)headerFields];
2073

    
2074
	CFRelease(headerFields);
2075
	
2076
	[self setResponseStatusCode:(int)CFHTTPMessageGetResponseStatusCode(message)];
2077
	[self setResponseStatusMessage:[(NSString *)CFHTTPMessageCopyResponseStatusLine(message) autorelease]];
2078
	
2079
	if ([self downloadCache] && ([[self downloadCache] canUseCachedDataForRequest:self])) {
2080
		// Read the response from the cache
2081
		[self useDataFromCache];
2082
		// Update the response headers (this will usually move the expiry date into the future)
2083
		[[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
2084
		CFRelease(message);
2085
		return;
2086
	}
2087

    
2088
	// Is the server response a challenge for credentials?
2089
	if ([self responseStatusCode] == 401) {
2090
		[self setAuthenticationNeeded:ASIHTTPAuthenticationNeeded];
2091
	} else if ([self responseStatusCode] == 407) {
2092
		[self setAuthenticationNeeded:ASIProxyAuthenticationNeeded];
2093
	}
2094
		
2095
	// Authentication succeeded, or no authentication was required
2096
	if (![self authenticationNeeded]) {
2097

    
2098
		// Did we get here without an authentication challenge? (which can happen when shouldPresentCredentialsBeforeChallenge is YES and basic auth was successful)
2099
		if (!requestAuthentication && [self username] && [self password] && [self useSessionPersistence]) {
2100
			
2101
			NSMutableDictionary *newCredentials = [NSMutableDictionary dictionaryWithCapacity:2];
2102
			[newCredentials setObject:[self username] forKey:(NSString *)kCFHTTPAuthenticationUsername];
2103
			[newCredentials setObject:[self password] forKey:(NSString *)kCFHTTPAuthenticationPassword];
2104
			
2105
			// Store the credentials in the session 
2106
			NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary];
2107
			[sessionCredentials setObject:newCredentials forKey:@"Credentials"];
2108
			[sessionCredentials setObject:[self url] forKey:@"URL"];
2109
			[sessionCredentials setObject:(NSString *)kCFHTTPAuthenticationSchemeBasic forKey:@"AuthenticationScheme"];
2110
			[[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials];
2111
		}
2112
	}
2113

    
2114
	// Read response textEncoding
2115
	[self parseStringEncodingFromHeaders];
2116

    
2117
	// Handle cookies
2118
	NSArray *newCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[self responseHeaders] forURL:[self url]];
2119
	[self setResponseCookies:newCookies];
2120
	
2121
	if ([self useCookiePersistence]) {
2122
		
2123
		// Store cookies in global persistent store
2124
		[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:newCookies forURL:[self url] mainDocumentURL:nil];
2125
		
2126
		// We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later
2127
		NSHTTPCookie *cookie;
2128
		for (cookie in newCookies) {
2129
			[ASIHTTPRequest addSessionCookie:cookie];
2130
		}
2131
	}
2132
	
2133
	// Do we need to redirect?
2134
	// Note that ASIHTTPRequest does not currently support 305 Use Proxy
2135
	if ([self shouldRedirect] && [responseHeaders valueForKey:@"Location"]) {
2136
		if (([self responseStatusCode] > 300 && [self responseStatusCode] < 304) || [self responseStatusCode] == 307) {
2137
            
2138
			[self performSelectorOnMainThread:@selector(requestRedirected) withObject:nil waitUntilDone:[NSThread isMainThread]];
2139
			
2140
			// By default, we redirect 301 and 302 response codes as GET requests
2141
			// According to RFC 2616 this is wrong, but this is what most browsers do, so it's probably what you're expecting to happen
2142
			// See also:
2143
			// http://allseeing-i.lighthouseapp.com/projects/27881/tickets/27-302-redirection-issue
2144
							
2145
			if ([self responseStatusCode] != 307 && (![self shouldUseRFC2616RedirectBehaviour] || [self responseStatusCode] == 303)) {
2146
				[self setRequestMethod:@"GET"];
2147
				[self setPostBody:nil];
2148
				[self setPostLength:0];
2149

    
2150
				// Perhaps there are other headers we should be preserving, but it's hard to know what we need to keep and what to throw away.
2151
				NSString *userAgentHeader = [[self requestHeaders] objectForKey:@"User-Agent"];
2152
				NSString *acceptHeader = [[self requestHeaders] objectForKey:@"Accept"];
2153
				[self setRequestHeaders:nil];
2154
				if (userAgentHeader) {
2155
					[self addRequestHeader:@"User-Agent" value:userAgentHeader];
2156
				}
2157
				if (acceptHeader) {
2158
					[self addRequestHeader:@"Accept" value:acceptHeader];
2159
				}
2160
				[self setHaveBuiltRequestHeaders:NO];
2161
			} else {
2162
			
2163
				// Force rebuild the cookie header incase we got some new cookies from this request
2164
				// All other request headers will remain as they are for 301 / 302 redirects
2165
				[self applyCookieHeader];
2166
			}
2167

    
2168
			// Force the redirected request to rebuild the request headers (if not a 303, it will re-use old ones, and add any new ones)
2169
			
2170
			[self setRedirectURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]];
2171
			[self setNeedsRedirect:YES];
2172
			
2173
			// Clear the request cookies
2174
			// This means manually added cookies will not be added to the redirect request - only those stored in the global persistent store
2175
			// But, this is probably the safest option - we might be redirecting to a different domain
2176
			[self setRequestCookies:[NSMutableArray array]];
2177
			
2178
			#if DEBUG_REQUEST_STATUS
2179
				NSLog(@"Request will redirect (code: %i): %@",[self responseStatusCode],self);
2180
			#endif
2181
			
2182
		}
2183
	}
2184

    
2185
	if (![self needsRedirect]) {
2186
		// See if we got a Content-length header
2187
		NSString *cLength = [responseHeaders valueForKey:@"Content-Length"];
2188
		ASIHTTPRequest *theRequest = self;
2189
		if ([self mainRequest]) {
2190
			theRequest = [self mainRequest];
2191
		}
2192

    
2193
		if (cLength) {
2194
			unsigned long long length = strtoull([cLength UTF8String], NULL, 0);
2195

    
2196
			// Workaround for Apache HEAD requests for dynamically generated content returning the wrong Content-Length when using gzip
2197
			if ([self mainRequest] && [self allowCompressedResponse] && length == 20 && [self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2198
				[[self mainRequest] setShowAccurateProgress:NO];
2199
				[[self mainRequest] incrementDownloadSizeBy:1];
2200

    
2201
			} else {
2202
				[theRequest setContentLength:length];
2203
				if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2204
					[theRequest incrementDownloadSizeBy:[theRequest contentLength]+[theRequest partialDownloadSize]];
2205
				}
2206
			}
2207

    
2208
		} else if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2209
			[theRequest setShowAccurateProgress:NO];
2210
			[theRequest incrementDownloadSizeBy:1];
2211
		}
2212
	}
2213

    
2214
	// Handle connection persistence
2215
	if ([self shouldAttemptPersistentConnection]) {
2216
		
2217
		NSString *connectionHeader = [[[self responseHeaders] objectForKey:@"Connection"] lowercaseString];
2218
		NSString *httpVersion = NSMakeCollectable([(NSString *)CFHTTPMessageCopyVersion(message) autorelease]);
2219
		
2220
		// Don't re-use the connection if the server is HTTP 1.0 and didn't send Connection: Keep-Alive
2221
		if (![httpVersion isEqualToString:(NSString *)kCFHTTPVersion1_0] || [connectionHeader isEqualToString:@"keep-alive"]) {
2222

    
2223
			// See if server explicitly told us to close the connection
2224
			if (![connectionHeader isEqualToString:@"close"]) {
2225
				
2226
				NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"];
2227
				
2228
				// If we got a keep alive header, we'll reuse the connection for as long as the server tells us
2229
				if (keepAliveHeader) { 
2230
					int timeout = 0;
2231
					int max = 0;
2232
					NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader];
2233
					[scanner scanString:@"timeout=" intoString:NULL];
2234
					[scanner scanInt:&timeout];
2235
					[scanner scanUpToString:@"max=" intoString:NULL];
2236
					[scanner scanString:@"max=" intoString:NULL];
2237
					[scanner scanInt:&max];
2238
					if (max > 5) {
2239
						[self setConnectionCanBeReused:YES];
2240
						[self setPersistentConnectionTimeoutSeconds:timeout];
2241
						#if DEBUG_PERSISTENT_CONNECTIONS
2242
							NSLog(@"Got a keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]);
2243
						#endif					
2244
					}
2245
				
2246
				// Otherwise, we'll assume we can keep this connection open
2247
				} else {
2248
					[self setConnectionCanBeReused:YES];
2249
					#if DEBUG_PERSISTENT_CONNECTIONS
2250
						NSLog(@"Got no keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]);
2251
					#endif
2252
				}
2253
			}
2254
		}
2255
	}
2256

    
2257
	CFRelease(message);
2258
	[self performSelectorOnMainThread:@selector(requestReceivedResponseHeaders:) withObject:[[[self responseHeaders] copy] autorelease] waitUntilDone:[NSThread isMainThread]];
2259
}
2260

    
2261
- (void)parseStringEncodingFromHeaders
2262
{
2263
	// Handle response text encoding
2264
	NSStringEncoding charset = 0;
2265
	NSString *mimeType = nil;
2266
	[[self class] parseMimeType:&mimeType andResponseEncoding:&charset fromContentType:[[self responseHeaders] valueForKey:@"Content-Type"]];
2267
	if (charset != 0) {
2268
		[self setResponseEncoding:charset];
2269
	} else {
2270
		[self setResponseEncoding:[self defaultResponseEncoding]];
2271
	}
2272
}
2273

    
2274
#pragma mark http authentication
2275

    
2276
- (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials
2277
{
2278
	NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
2279
	if (authenticationCredentials) {
2280
		[ASIHTTPRequest saveCredentials:authenticationCredentials forProxy:[self proxyHost] port:[self proxyPort] realm:[self proxyAuthenticationRealm]];
2281
	}	
2282
}
2283

    
2284

    
2285
- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials
2286
{
2287
	NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
2288
	
2289
	if (authenticationCredentials) {
2290
		[ASIHTTPRequest saveCredentials:authenticationCredentials forHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
2291
	}	
2292
}
2293

    
2294
- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials
2295
{
2296
	[self setProxyAuthenticationRetryCount:[self proxyAuthenticationRetryCount]+1];
2297
	
2298
	if (newCredentials && proxyAuthentication && request) {
2299

    
2300
		// Apply whatever credentials we've built up to the old request
2301
		if (CFHTTPMessageApplyCredentialDictionary(request, proxyAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
2302
			
2303
			//If we have credentials and they're ok, let's save them to the keychain
2304
			if (useKeychainPersistence) {
2305
				[self saveProxyCredentialsToKeychain:newCredentials];
2306
			}
2307
			if (useSessionPersistence) {
2308
				NSMutableDictionary *sessionProxyCredentials = [NSMutableDictionary dictionary];
2309
				[sessionProxyCredentials setObject:(id)proxyAuthentication forKey:@"Authentication"];
2310
				[sessionProxyCredentials setObject:newCredentials forKey:@"Credentials"];
2311
				[sessionProxyCredentials setObject:[self proxyHost] forKey:@"Host"];
2312
				[sessionProxyCredentials setObject:[NSNumber numberWithInt:[self proxyPort]] forKey:@"Port"];
2313
				[sessionProxyCredentials setObject:[self proxyAuthenticationScheme] forKey:@"AuthenticationScheme"];
2314
				[[self class] storeProxyAuthenticationCredentialsInSessionStore:sessionProxyCredentials];
2315
			}
2316
			[self setProxyCredentials:newCredentials];
2317
			return YES;
2318
		} else {
2319
			[[self class] removeProxyAuthenticationCredentialsFromSessionStore:newCredentials];
2320
		}
2321
	}
2322
	return NO;
2323
}
2324

    
2325
- (BOOL)applyCredentials:(NSDictionary *)newCredentials
2326
{
2327
	[self setAuthenticationRetryCount:[self authenticationRetryCount]+1];
2328
	
2329
	if (newCredentials && requestAuthentication && request) {
2330
		// Apply whatever credentials we've built up to the old request
2331
		if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
2332
			
2333
			//If we have credentials and they're ok, let's save them to the keychain
2334
			if (useKeychainPersistence) {
2335
				[self saveCredentialsToKeychain:newCredentials];
2336
			}
2337
			if (useSessionPersistence) {
2338
				
2339
				NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary];
2340
				[sessionCredentials setObject:(id)requestAuthentication forKey:@"Authentication"];
2341
				[sessionCredentials setObject:newCredentials forKey:@"Credentials"];
2342
				[sessionCredentials setObject:[self url] forKey:@"URL"];
2343
				[sessionCredentials setObject:[self authenticationScheme] forKey:@"AuthenticationScheme"];
2344
				if ([self authenticationRealm]) {
2345
					[sessionCredentials setObject:[self authenticationRealm] forKey:@"AuthenticationRealm"];
2346
				}
2347
				[[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials];
2348

    
2349
			}
2350
			[self setRequestCredentials:newCredentials];
2351
			return YES;
2352
		} else {
2353
			[[self class] removeAuthenticationCredentialsFromSessionStore:newCredentials];
2354
		}
2355
	}
2356
	return NO;
2357
}
2358

    
2359
- (NSMutableDictionary *)findProxyCredentials
2360
{
2361
	NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
2362
	
2363
	// Is an account domain needed? (used currently for NTLM only)
2364
	if (CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) {
2365
		if (![self proxyDomain]) {
2366
			[self setProxyDomain:@""];
2367
		}
2368
		[newCredentials setObject:[self proxyDomain] forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
2369
	}
2370
	
2371
	NSString *user = nil;
2372
	NSString *pass = nil;
2373
	
2374

    
2375
	// If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
2376
	if ([self mainRequest] && [[self mainRequest] proxyUsername] && [[self mainRequest] proxyPassword]) {
2377
		user = [[self mainRequest] proxyUsername];
2378
		pass = [[self mainRequest] proxyPassword];
2379
		
2380
		// Let's try to use the ones set in this object
2381
	} else if ([self proxyUsername] && [self proxyPassword]) {
2382
		user = [self proxyUsername];
2383
		pass = [self proxyPassword];
2384
	}		
2385

    
2386
	
2387
	// Ok, that didn't work, let's try the keychain
2388
	// For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence
2389
	if ((!user || !pass)) {
2390
		NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForProxy:[self proxyHost] port:[self proxyPort] protocol:[[self url] scheme] realm:[self proxyAuthenticationRealm]];
2391
		if (authenticationCredentials) {
2392
			user = [authenticationCredentials user];
2393
			pass = [authenticationCredentials password];
2394
		}
2395
		
2396
	}
2397
	
2398
	// If we have a username and password, let's apply them to the request and continue
2399
	if (user && pass) {
2400
		
2401
		[newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
2402
		[newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
2403
		return newCredentials;
2404
	}
2405
	return nil;
2406
}
2407

    
2408

    
2409
- (NSMutableDictionary *)findCredentials
2410
{
2411
	NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
2412
	
2413
	// Is an account domain needed? (used currently for NTLM only)
2414
	if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
2415
		if (!domain) {
2416
			[self setDomain:@""];
2417
		}
2418
		[newCredentials setObject:domain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
2419
	}
2420
	
2421
	// First, let's look at the url to see if the username and password were included
2422
	NSString *user = [[self url] user];
2423
	NSString *pass = [[self url] password];
2424
	
2425
	// If the username and password weren't in the url
2426
	if (!user || !pass) {
2427
		
2428
		// If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
2429
		if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) {
2430
			user = [[self mainRequest] username];
2431
			pass = [[self mainRequest] password];
2432
			
2433
		// Let's try to use the ones set in this object
2434
		} else if ([self username] && [self password]) {
2435
			user = [self username];
2436
			pass = [self password];
2437
		}		
2438
		
2439
	}
2440
	
2441
	// Ok, that didn't work, let's try the keychain
2442
	if ((!user || !pass) && useKeychainPersistence) {
2443
		NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
2444
		if (authenticationCredentials) {
2445
			user = [authenticationCredentials user];
2446
			pass = [authenticationCredentials password];
2447
		}
2448
		
2449
	}
2450
	
2451
	// If we have a username and password, let's apply them to the request and continue
2452
	if (user && pass) {
2453
		
2454
		[newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
2455
		[newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
2456
		return newCredentials;
2457
	}
2458
	return nil;
2459
}
2460

    
2461
// Called by delegate or authentication dialog to resume loading once authentication info has been populated
2462
- (void)retryUsingSuppliedCredentials
2463
{
2464
	//If the url was changed by the delegate, our CFHTTPMessageRef will be NULL and we'll go back to the start
2465
	if (!request) {
2466
		[self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2467
		return;
2468
	}
2469
	[self performSelector:@selector(attemptToApplyCredentialsAndResume) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2470
}
2471

    
2472
// Called by delegate or authentication dialog to cancel authentication
2473
- (void)cancelAuthentication
2474
{
2475
	[self performSelector:@selector(failAuthentication) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2476
}
2477

    
2478
- (void)failAuthentication
2479
{
2480
	[self failWithError:ASIAuthenticationError];
2481
}
2482

    
2483
- (BOOL)showProxyAuthenticationDialog
2484
{
2485
// Mac authentication dialog coming soon!
2486
#if TARGET_OS_IPHONE
2487
	if ([self shouldPresentProxyAuthenticationDialog]) {
2488
		[ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
2489
		return YES;
2490
	}
2491
	return NO;
2492
#else
2493
	return NO;
2494
#endif
2495
}
2496

    
2497

    
2498
- (BOOL)willAskDelegateForProxyCredentials
2499
{
2500
	// If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
2501
	// Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
2502
	id authenticationDelegate = [self delegate];
2503
	if (!authenticationDelegate) {
2504
		authenticationDelegate = [self queue];
2505
	}
2506
	
2507
	BOOL delegateOrBlockWillHandleAuthentication = NO;
2508

    
2509
	if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
2510
		delegateOrBlockWillHandleAuthentication = YES;
2511
	}
2512

    
2513
	#if NS_BLOCKS_AVAILABLE
2514
	if(proxyAuthenticationNeededBlock){
2515
		delegateOrBlockWillHandleAuthentication = YES;
2516
	}
2517
	#endif
2518

    
2519
	if (delegateOrBlockWillHandleAuthentication) {
2520
		[self performSelectorOnMainThread:@selector(askDelegateForProxyCredentials) withObject:nil waitUntilDone:NO];
2521
	}
2522
	
2523
	return delegateOrBlockWillHandleAuthentication;
2524
}
2525

    
2526
/* ALWAYS CALLED ON MAIN THREAD! */
2527
- (void)askDelegateForProxyCredentials
2528
{
2529
	id authenticationDelegate = [self delegate];
2530
	if (!authenticationDelegate) {
2531
		authenticationDelegate = [self queue];
2532
	}
2533
	if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
2534
		[authenticationDelegate performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:self];
2535
		return;
2536
	}
2537
	#if NS_BLOCKS_AVAILABLE
2538
	if(proxyAuthenticationNeededBlock){
2539
		proxyAuthenticationNeededBlock();
2540
	}
2541
	#endif
2542
}
2543

    
2544

    
2545
- (BOOL)willAskDelegateForCredentials
2546
{
2547
	// If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
2548
	// Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
2549
	id authenticationDelegate = [self delegate];
2550
	if (!authenticationDelegate) {
2551
		authenticationDelegate = [self queue];
2552
	}
2553

    
2554
	BOOL delegateOrBlockWillHandleAuthentication = NO;
2555

    
2556
	if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) {
2557
		delegateOrBlockWillHandleAuthentication = YES;
2558
	}
2559

    
2560
	#if NS_BLOCKS_AVAILABLE
2561
	if (authenticationNeededBlock) {
2562
		delegateOrBlockWillHandleAuthentication = YES;
2563
	}
2564
	#endif
2565

    
2566
	if (delegateOrBlockWillHandleAuthentication) {
2567
		[self performSelectorOnMainThread:@selector(askDelegateForCredentials) withObject:nil waitUntilDone:NO];
2568
	}
2569
	return delegateOrBlockWillHandleAuthentication;
2570
}
2571

    
2572
/* ALWAYS CALLED ON MAIN THREAD! */
2573
- (void)askDelegateForCredentials
2574
{
2575
	id authenticationDelegate = [self delegate];
2576
	if (!authenticationDelegate) {
2577
		authenticationDelegate = [self queue];
2578
	}
2579
	
2580
	if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) {
2581
		[authenticationDelegate performSelector:@selector(authenticationNeededForRequest:) withObject:self];
2582
		return;
2583
	}
2584
	
2585
	#if NS_BLOCKS_AVAILABLE
2586
	if (authenticationNeededBlock) {
2587
		authenticationNeededBlock();
2588
	}
2589
	#endif	
2590
}
2591

    
2592
- (void)attemptToApplyProxyCredentialsAndResume
2593
{
2594
	
2595
	if ([self error] || [self isCancelled]) {
2596
		return;
2597
	}
2598
	
2599
	// Read authentication data
2600
	if (!proxyAuthentication) {
2601
		CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader);
2602
		proxyAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
2603
		CFRelease(responseHeader);
2604
		[self setProxyAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(proxyAuthentication) autorelease]];
2605
	}
2606
	
2607
	// If we haven't got a CFHTTPAuthenticationRef by now, something is badly wrong, so we'll have to give up
2608
	if (!proxyAuthentication) {
2609
		[self cancelLoad];
2610
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
2611
		return;
2612
	}
2613
	
2614
	// Get the authentication realm
2615
	[self setProxyAuthenticationRealm:nil];
2616
	if (!CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) {
2617
		[self setProxyAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(proxyAuthentication) autorelease]];
2618
	}
2619
	
2620
	// See if authentication is valid
2621
	CFStreamError err;		
2622
	if (!CFHTTPAuthenticationIsValid(proxyAuthentication, &err)) {
2623
		
2624
		CFRelease(proxyAuthentication);
2625
		proxyAuthentication = NULL;
2626
		
2627
		// check for bad credentials, so we can give the delegate a chance to replace them
2628
		if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
2629
			
2630
			// Prevent more than one request from asking for credentials at once
2631
			[delegateAuthenticationLock lock];
2632
			
2633
			// We know the credentials we just presented are bad, we should remove them from the session store too
2634
			[[self class] removeProxyAuthenticationCredentialsFromSessionStore:proxyCredentials];
2635
			[self setProxyCredentials:nil];
2636
			
2637
			
2638
			// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
2639
			if ([self error] || [self isCancelled]) {
2640
				[delegateAuthenticationLock unlock];
2641
				return;
2642
			}
2643
			
2644
			
2645
			// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
2646
			if ([self useSessionPersistence]) {
2647
				NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials];
2648
				if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) {
2649
					[delegateAuthenticationLock unlock];
2650
					[self startRequest];
2651
					return;
2652
				}
2653
			}
2654
			
2655
			[self setLastActivityTime:nil];
2656
			
2657
			if ([self willAskDelegateForProxyCredentials]) {
2658
				[self attemptToApplyProxyCredentialsAndResume];
2659
				[delegateAuthenticationLock unlock];
2660
				return;
2661
			}
2662
			if ([self showProxyAuthenticationDialog]) {
2663
				[self attemptToApplyProxyCredentialsAndResume];
2664
				[delegateAuthenticationLock unlock];
2665
				return;
2666
			}
2667
			[delegateAuthenticationLock unlock];
2668
		}
2669
		[self cancelLoad];
2670
		[self failWithError:ASIAuthenticationError];
2671
		return;
2672
	}
2673

    
2674
	[self cancelLoad];
2675
	
2676
	if (proxyCredentials) {
2677
		
2678
		// We use startRequest rather than starting all over again in load request because NTLM requires we reuse the request
2679
		if ((([self proxyAuthenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self proxyAuthenticationRetryCount] < 2) && [self applyProxyCredentials:proxyCredentials]) {
2680
			[self startRequest];
2681
			
2682
		// We've failed NTLM authentication twice, we should assume our credentials are wrong
2683
		} else if ([self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self proxyAuthenticationRetryCount] == 2) {
2684
			[self failWithError:ASIAuthenticationError];
2685
			
2686
		// Something went wrong, we'll have to give up
2687
		} else {
2688
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]];
2689
		}
2690
		
2691
	// Are a user name & password needed?
2692
	}  else if (CFHTTPAuthenticationRequiresUserNameAndPassword(proxyAuthentication)) {
2693
		
2694
		// Prevent more than one request from asking for credentials at once
2695
		[delegateAuthenticationLock lock];
2696
		
2697
		// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
2698
		if ([self error] || [self isCancelled]) {
2699
			[delegateAuthenticationLock unlock];
2700
			return;
2701
		}
2702
		
2703
		// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
2704
		if ([self useSessionPersistence]) {
2705
			NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials];
2706
			if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) {
2707
				[delegateAuthenticationLock unlock];
2708
				[self startRequest];
2709
				return;
2710
			}
2711
		}
2712
		
2713
		NSMutableDictionary *newCredentials = [self findProxyCredentials];
2714
		
2715
		//If we have some credentials to use let's apply them to the request and continue
2716
		if (newCredentials) {
2717
			
2718
			if ([self applyProxyCredentials:newCredentials]) {
2719
				[delegateAuthenticationLock unlock];
2720
				[self startRequest];
2721
			} else {
2722
				[delegateAuthenticationLock unlock];
2723
				[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]];
2724
			}
2725
			
2726
			return;
2727
		}
2728
		
2729
		if ([self willAskDelegateForProxyCredentials]) {
2730
			[delegateAuthenticationLock unlock];
2731
			return;
2732
		}
2733
		
2734
		if ([self showProxyAuthenticationDialog]) {
2735
			[delegateAuthenticationLock unlock];
2736
			return;
2737
		}
2738
		[delegateAuthenticationLock unlock];
2739
		
2740
		// The delegate isn't interested and we aren't showing the authentication dialog, we'll have to give up
2741
		[self failWithError:ASIAuthenticationError];
2742
		return;
2743
	}
2744
	
2745
}
2746

    
2747
- (BOOL)showAuthenticationDialog
2748
{
2749
// Mac authentication dialog coming soon!
2750
#if TARGET_OS_IPHONE
2751
	if ([self shouldPresentAuthenticationDialog]) {
2752
		[ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
2753
		return YES;
2754
	}
2755
	return NO;
2756
#else
2757
	return NO;
2758
#endif
2759
}
2760

    
2761

    
2762

    
2763
- (void)attemptToApplyCredentialsAndResume
2764
{
2765
	if ([self error] || [self isCancelled]) {
2766
		return;
2767
	}
2768
	
2769
	if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) {
2770
		[self attemptToApplyProxyCredentialsAndResume];
2771
		return;
2772
	}
2773
	
2774
	// Read authentication data
2775
	if (!requestAuthentication) {
2776
		CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader);
2777
		requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
2778
		CFRelease(responseHeader);
2779
		[self setAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(requestAuthentication) autorelease]];
2780
	}
2781
	
2782
	if (!requestAuthentication) {
2783
		[self cancelLoad];
2784
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
2785
		return;
2786
	}
2787
	
2788
	// Get the authentication realm
2789
	[self setAuthenticationRealm:nil];
2790
	if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
2791
		[self setAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication) autorelease]];
2792
	}
2793
	
2794
	// See if authentication is valid
2795
	CFStreamError err;		
2796
	if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) {
2797
		
2798
		CFRelease(requestAuthentication);
2799
		requestAuthentication = NULL;
2800
		
2801
		// check for bad credentials, so we can give the delegate a chance to replace them
2802
		if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
2803
			
2804
			// Prevent more than one request from asking for credentials at once
2805
			[delegateAuthenticationLock lock];
2806
			
2807
			// We know the credentials we just presented are bad, we should remove them from the session store too
2808
			[[self class] removeAuthenticationCredentialsFromSessionStore:requestCredentials];
2809
			[self setRequestCredentials:nil];
2810
			
2811
			// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
2812
			if ([self error] || [self isCancelled]) {
2813
				[delegateAuthenticationLock unlock];
2814
				return;
2815
			}
2816
			
2817
			// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
2818
			if ([self useSessionPersistence]) {
2819
				NSDictionary *credentials = [self findSessionAuthenticationCredentials];
2820
				if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) {
2821
					[delegateAuthenticationLock unlock];
2822
					[self startRequest];
2823
					return;
2824
				}
2825
			}
2826
			
2827
			
2828
			
2829
			[self setLastActivityTime:nil];
2830
			
2831
			if ([self willAskDelegateForCredentials]) {
2832
				[delegateAuthenticationLock unlock];
2833
				return;
2834
			}
2835
			if ([self showAuthenticationDialog]) {
2836
				[delegateAuthenticationLock unlock];
2837
				return;
2838
			}
2839
			[delegateAuthenticationLock unlock];
2840
		}
2841
		[self cancelLoad];
2842
		[self failWithError:ASIAuthenticationError];
2843
		return;
2844
	}
2845
	
2846
	[self cancelLoad];
2847
	
2848
	if (requestCredentials) {
2849
		
2850
		if ((([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self authenticationRetryCount] < 2) && [self applyCredentials:requestCredentials]) {
2851
			[self startRequest];
2852
			
2853
			// We've failed NTLM authentication twice, we should assume our credentials are wrong
2854
		} else if ([self authenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self authenticationRetryCount ] == 2) {
2855
			[self failWithError:ASIAuthenticationError];
2856
			
2857
		} else {
2858
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
2859
		}
2860
		
2861
		// Are a user name & password needed?
2862
	}  else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) {
2863
		
2864
		// Prevent more than one request from asking for credentials at once
2865
		[delegateAuthenticationLock lock];
2866
		
2867
		// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
2868
		if ([self error] || [self isCancelled]) {
2869
			[delegateAuthenticationLock unlock];
2870
			return;
2871
		}
2872
		
2873
		// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
2874
		if ([self useSessionPersistence]) {
2875
			NSDictionary *credentials = [self findSessionAuthenticationCredentials];
2876
			if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) {
2877
				[delegateAuthenticationLock unlock];
2878
				[self startRequest];
2879
				return;
2880
			}
2881
		}
2882
		
2883

    
2884
		NSMutableDictionary *newCredentials = [self findCredentials];
2885
		
2886
		//If we have some credentials to use let's apply them to the request and continue
2887
		if (newCredentials) {
2888
			
2889
			if ([self applyCredentials:newCredentials]) {
2890
				[delegateAuthenticationLock unlock];
2891
				[self startRequest];
2892
			} else {
2893
				[delegateAuthenticationLock unlock];
2894
				[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
2895
			}
2896
			return;
2897
		}
2898
		if ([self willAskDelegateForCredentials]) {
2899
			[delegateAuthenticationLock unlock];
2900
			return;
2901
		}
2902
		
2903
		if ([self showAuthenticationDialog]) {
2904
			[delegateAuthenticationLock unlock];
2905
			return;
2906
		}
2907
		[delegateAuthenticationLock unlock];
2908
		
2909
		[self failWithError:ASIAuthenticationError];
2910

    
2911
		return;
2912
	}
2913
	
2914
}
2915

    
2916
- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword
2917
{
2918
	[self addRequestHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@",[ASIHTTPRequest base64forData:[[NSString stringWithFormat:@"%@:%@",theUsername,thePassword] dataUsingEncoding:NSUTF8StringEncoding]]]];	
2919
}
2920

    
2921

    
2922
#pragma mark stream status handlers
2923

    
2924
- (void)handleNetworkEvent:(CFStreamEventType)type
2925
{	
2926
	[[self cancelledLock] lock];
2927
	
2928
	if ([self complete] || [self isCancelled]) {
2929
		[[self cancelledLock] unlock];
2930
		return;
2931
	}
2932
	
2933
	CFRetain(self);
2934

    
2935
    // Dispatch the stream events.
2936
    switch (type) {
2937
        case kCFStreamEventHasBytesAvailable:
2938
            [self handleBytesAvailable];
2939
            break;
2940
            
2941
        case kCFStreamEventEndEncountered:
2942
            [self handleStreamComplete];
2943
            break;
2944
            
2945
        case kCFStreamEventErrorOccurred:
2946
            [self handleStreamError];
2947
            break;
2948
            
2949
        default:
2950
            break;
2951
    }
2952
	
2953
	[self performThrottling];
2954
	
2955
	[[self cancelledLock] unlock];
2956
	
2957
	if ([self downloadComplete] && [self needsRedirect]) {
2958

    
2959
		// We must lock again to ensure delegate / queue aren't changed while we check them
2960
		[[self cancelledLock] lock];
2961
		// Here we perform an initial check to see if either the delegate or the queue wants to be asked about the redirect, because if not we should redirect straight away
2962
		// We will check again on the main thread later
2963
		BOOL needToAskDelegateAboutRedirect = (([self delegate] && [[self delegate] respondsToSelector:[self willRedirectSelector]]) || ([self queue] && [[self queue] respondsToSelector:@selector(request:willRedirectToURL:)]));
2964
		[[self cancelledLock] unlock];
2965

    
2966
		// Either the delegate or the queue's delegate is interested in being told when we are about to redirect
2967
		if (needToAskDelegateAboutRedirect) {
2968
			NSURL *newURL = [[[self redirectURL] copy] autorelease];
2969
			[self setRedirectURL:nil];
2970
			[self performSelectorOnMainThread:@selector(requestWillRedirectToURL:) withObject:newURL waitUntilDone:[NSThread isMainThread]];
2971

    
2972
		// If neither the delegate nor the queue's delegate implement request:willRedirectToURL:, we will redirect automatically
2973
		} else {
2974
			[self performRedirect];
2975
		}
2976
	} else if ([self downloadComplete] && [self authenticationNeeded]) {
2977
		[self attemptToApplyCredentialsAndResume];
2978
	}
2979

    
2980
	CFRelease(self);
2981
}
2982

    
2983
- (void)handleBytesAvailable
2984
{
2985
	if (![self responseHeaders]) {
2986
		[self readResponseHeaders];
2987
	}
2988
	
2989
	// If we've cancelled the load part way through (for example, after deciding to use a cached version)
2990
	if ([self complete]) {
2991
		return;
2992
	}
2993
	
2994
	// In certain (presumably very rare) circumstances, handleBytesAvailable seems to be called when there isn't actually any data available
2995
	// We'll check that there is actually data available to prevent blocking on CFReadStreamRead()
2996
	// So far, I've only seen this in the stress tests, so it might never happen in real-world situations.
2997
	if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)[self readStream])) {
2998
		return;
2999
	}
3000

    
3001
	long long bufferSize = 16384;
3002
	if (contentLength > 262144) {
3003
		bufferSize = 262144;
3004
	} else if (contentLength > 65536) {
3005
		bufferSize = 65536;
3006
	}
3007
	
3008
	// Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active
3009
	// This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit
3010
	
3011
	if ([[self class] isBandwidthThrottled]) {
3012
		[bandwidthThrottlingLock lock];
3013
		if (maxBandwidthPerSecond > 0) {
3014
			long long maxiumumSize  = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
3015
			if (maxiumumSize < 0) {
3016
				// We aren't supposed to read any more data right now, but we'll read a single byte anyway so the CFNetwork's buffer isn't full
3017
				bufferSize = 1;
3018
			} else if (maxiumumSize/4 < bufferSize) {
3019
				// We were going to fetch more data that we should be allowed, so we'll reduce the size of our read
3020
				bufferSize = maxiumumSize/4;
3021
			}
3022
		}
3023
		if (bufferSize < 1) {
3024
			bufferSize = 1;
3025
		}
3026
		[bandwidthThrottlingLock unlock];
3027
	}
3028
	
3029
	
3030
    UInt8 buffer[bufferSize];
3031
    NSInteger bytesRead = [[self readStream] read:buffer maxLength:sizeof(buffer)];
3032

    
3033
    // Less than zero is an error
3034
    if (bytesRead < 0) {
3035
        [self handleStreamError];
3036
		
3037
	// If zero bytes were read, wait for the EOF to come.
3038
    } else if (bytesRead) {
3039

    
3040
		// If we are inflating the response on the fly
3041
		NSData *inflatedData = nil;
3042
		if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3043
			if (![self dataDecompressor]) {
3044
				[self setDataDecompressor:[ASIDataDecompressor decompressor]];
3045
			}
3046
			NSError *err = nil;
3047
			inflatedData = [[self dataDecompressor] uncompressBytes:buffer length:bytesRead error:&err];
3048
			if (err) {
3049
				[self failWithError:err];
3050
				return;
3051
			}
3052
		}
3053
		
3054
		[self setTotalBytesRead:[self totalBytesRead]+bytesRead];
3055
		[self setLastActivityTime:[NSDate date]];
3056

    
3057
		// For bandwidth measurement / throttling
3058
		[ASIHTTPRequest incrementBandwidthUsedInLastSecond:bytesRead];
3059
		
3060
		// If we need to redirect, and have automatic redirect on, and might be resuming a download, let's do nothing with the content
3061
		if ([self needsRedirect] && [self shouldRedirect] && [self allowResumeForFileDownloads]) {
3062
			return;
3063
		}
3064
		
3065
		BOOL dataWillBeHandledExternally = NO;
3066
		if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) {
3067
			dataWillBeHandledExternally = YES;
3068
		}
3069
		#if NS_BLOCKS_AVAILABLE
3070
		if (dataReceivedBlock) {
3071
			dataWillBeHandledExternally = YES;
3072
		}
3073
		#endif
3074
		// Does the delegate want to handle the data manually?
3075
		if (dataWillBeHandledExternally) {
3076

    
3077
			NSData *data = nil;
3078
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3079
				data = inflatedData;
3080
			} else {
3081
				data = [NSData dataWithBytes:buffer length:bytesRead];
3082
			}
3083
			[self performSelectorOnMainThread:@selector(passOnReceivedData:) withObject:data waitUntilDone:[NSThread isMainThread]];
3084
			
3085
		// Are we downloading to a file?
3086
		} else if ([self downloadDestinationPath]) {
3087
			BOOL append = NO;
3088
			if (![self fileDownloadOutputStream]) {
3089
				if (![self temporaryFileDownloadPath]) {
3090
					[self setTemporaryFileDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
3091
				} else if ([self allowResumeForFileDownloads] && [[self requestHeaders] objectForKey:@"Range"]) {
3092
					if ([[self responseHeaders] objectForKey:@"Content-Range"]) {
3093
						append = YES;
3094
					} else {
3095
						[self incrementDownloadSizeBy:-[self partialDownloadSize]];
3096
						[self setPartialDownloadSize:0];
3097
					}
3098
				}
3099

    
3100
				[self setFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryFileDownloadPath] append:append] autorelease]];
3101
				[[self fileDownloadOutputStream] open];
3102

    
3103
			}
3104
			[[self fileDownloadOutputStream] write:buffer maxLength:bytesRead];
3105

    
3106
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3107
				
3108
				if (![self inflatedFileDownloadOutputStream]) {
3109
					if (![self temporaryUncompressedDataDownloadPath]) {
3110
						[self setTemporaryUncompressedDataDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
3111
					}
3112
					
3113
					[self setInflatedFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryUncompressedDataDownloadPath] append:append] autorelease]];
3114
					[[self inflatedFileDownloadOutputStream] open];
3115
				}
3116

    
3117
				[[self inflatedFileDownloadOutputStream] write:[inflatedData bytes] maxLength:[inflatedData length]];
3118
			}
3119

    
3120
			
3121
		//Otherwise, let's add the data to our in-memory store
3122
		} else {
3123
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3124
				[rawResponseData appendData:inflatedData];
3125
			} else {
3126
				[rawResponseData appendBytes:buffer length:bytesRead];
3127
			}
3128
		}
3129
    }
3130
}
3131

    
3132
- (void)handleStreamComplete
3133
{	
3134

    
3135
#if DEBUG_REQUEST_STATUS
3136
	NSLog(@"Request %@ finished downloading data (%qu bytes)",self, [self totalBytesRead]);
3137
#endif
3138
	[self setStatusTimer:nil];
3139
	[self setDownloadComplete:YES];
3140
	
3141
	if (![self responseHeaders]) {
3142
		[self readResponseHeaders];
3143
	}
3144

    
3145
	[progressLock lock];	
3146
	// Find out how much data we've uploaded so far
3147
	[self setLastBytesSent:totalBytesSent];	
3148
	[self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]];
3149
	[self setComplete:YES];
3150
	if (![self contentLength]) {
3151
		[self setContentLength:[self totalBytesRead]];
3152
	}
3153
	[self updateProgressIndicators];
3154

    
3155
	
3156
	[[self postBodyReadStream] close];
3157
	[self setPostBodyReadStream:nil];
3158
	
3159
	[self setDataDecompressor:nil];
3160

    
3161
	NSError *fileError = nil;
3162
	
3163
	// Delete up the request body temporary file, if it exists
3164
	if ([self didCreateTemporaryPostDataFile] && ![self authenticationNeeded]) {
3165
		[self removeTemporaryUploadFile];
3166
		[self removeTemporaryCompressedUploadFile];
3167
	}
3168
	
3169
	// Close the output stream as we're done writing to the file
3170
	if ([self temporaryFileDownloadPath]) {
3171
		
3172
		[[self fileDownloadOutputStream] close];
3173
		[self setFileDownloadOutputStream:nil];
3174

    
3175
		[[self inflatedFileDownloadOutputStream] close];
3176
		[self setInflatedFileDownloadOutputStream:nil];
3177

    
3178
		// If we are going to redirect and we are resuming, let's ignore this download
3179
		if ([self shouldRedirect] && [self needsRedirect] && [self allowResumeForFileDownloads]) {
3180
		
3181
		} else if ([self isResponseCompressed]) {
3182
			
3183
			// Decompress the file directly to the destination path
3184
			if ([self shouldWaitToInflateCompressedResponses]) {
3185
				[ASIDataDecompressor uncompressDataFromFile:[self temporaryFileDownloadPath] toFile:[self downloadDestinationPath] error:&fileError];
3186

    
3187
			// Response should already have been inflated, move the temporary file to the destination path
3188
			} else {
3189
				NSError *moveError = nil;
3190
				[[NSFileManager defaultManager] moveItemAtPath:[self temporaryUncompressedDataDownloadPath] toPath:[self downloadDestinationPath] error:&moveError];
3191
				if (moveError) {
3192
					fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",[self temporaryFileDownloadPath],[self downloadDestinationPath]],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
3193
				}
3194
				[self setTemporaryUncompressedDataDownloadPath:nil];
3195

    
3196
			}
3197
			[self removeTemporaryDownloadFile];
3198

    
3199
		} else {
3200
	
3201
			//Remove any file at the destination path
3202
			NSError *moveError = nil;
3203
			if (![[self class] removeFileAtPath:[self downloadDestinationPath] error:&moveError]) {
3204
				fileError = moveError;
3205

    
3206
			}
3207

    
3208
			//Move the temporary file to the destination path
3209
			if (!fileError) {
3210
				[[NSFileManager defaultManager] moveItemAtPath:[self temporaryFileDownloadPath] toPath:[self downloadDestinationPath] error:&moveError];
3211
				if (moveError) {
3212
					fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",[self temporaryFileDownloadPath],[self downloadDestinationPath]],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
3213
				}
3214
				[self setTemporaryFileDownloadPath:nil];
3215
			}
3216
			
3217
		}
3218
	}
3219
	
3220
	// Save to the cache
3221
	if ([self downloadCache] && ![self didUseCachedResponse]) {
3222
		[[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
3223
	}
3224
	
3225
	[progressLock unlock];
3226

    
3227
	
3228
	[connectionsLock lock];
3229
	if (![self connectionCanBeReused]) {
3230
		[self unscheduleReadStream];
3231
	}
3232
	#if DEBUG_PERSISTENT_CONNECTIONS
3233
	NSLog(@"Request #%@ finished using connection #%@",[self requestID], [[self connectionInfo] objectForKey:@"id"]);
3234
	#endif
3235
	[[self connectionInfo] removeObjectForKey:@"request"];
3236
	[[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"];
3237
	[connectionsLock unlock];
3238
	
3239
	if (![self authenticationNeeded]) {
3240
		[self destroyReadStream];
3241
	}
3242
	
3243

    
3244
	if (![self needsRedirect] && ![self authenticationNeeded] && ![self didUseCachedResponse]) {
3245
		
3246
		if (fileError) {
3247
			[self failWithError:fileError];
3248
		} else {
3249
			[self performSelectorOnMainThread:@selector(requestFinished) withObject:nil waitUntilDone:[NSThread isMainThread]];
3250
		}
3251

    
3252
		[self markAsFinished];
3253
		
3254
	// If request has asked delegate or ASIAuthenticationDialog for credentials
3255
	} else if ([self authenticationNeeded]) {
3256
		CFRunLoopStop(CFRunLoopGetCurrent());
3257
	}
3258

    
3259
}
3260

    
3261
- (void)markAsFinished
3262
{
3263
	// Autoreleased requests may well be dealloced here otherwise
3264
	CFRetain(self);
3265

    
3266
	// dealloc won't be called when running with GC, so we'll clean these up now
3267
	if (request) {
3268
		CFMakeCollectable(request);
3269
	}
3270
	if (requestAuthentication) {
3271
		CFMakeCollectable(requestAuthentication);
3272
	}
3273
	if (proxyAuthentication) {
3274
		CFMakeCollectable(proxyAuthentication);
3275
	}
3276

    
3277
    BOOL wasInProgress = inProgress;
3278
    BOOL wasFinished = finished;
3279

    
3280
    if (!wasFinished)
3281
        [self willChangeValueForKey:@"isFinished"];
3282
    if (wasInProgress)
3283
        [self willChangeValueForKey:@"isExecuting"];
3284

    
3285
	[self setInProgress:NO];
3286
    finished = YES;
3287

    
3288
    if (wasInProgress)
3289
        [self didChangeValueForKey:@"isExecuting"];
3290
    if (!wasFinished)
3291
        [self didChangeValueForKey:@"isFinished"];
3292

    
3293
	CFRunLoopStop(CFRunLoopGetCurrent());
3294

    
3295
	#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
3296
	if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
3297
		dispatch_async(dispatch_get_main_queue(), ^{
3298
			if (backgroundTask != UIBackgroundTaskInvalid) {
3299
				[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
3300
				backgroundTask = UIBackgroundTaskInvalid;
3301
			}
3302
		});
3303
	}
3304
	#endif
3305
	CFRelease(self);
3306
}
3307

    
3308
- (void)useDataFromCache
3309
{
3310
	NSDictionary *headers = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
3311
	NSString *dataPath = [[self downloadCache] pathToCachedResponseDataForURL:[self url]];
3312

    
3313
	ASIHTTPRequest *theRequest = self;
3314
	if ([self mainRequest]) {
3315
		theRequest = [self mainRequest];
3316
	}
3317

    
3318
	if (headers && dataPath) {
3319

    
3320
		// only 200 responses are stored in the cache, so let the client know
3321
		// this was a successful response
3322
		[self setResponseStatusCode:200];
3323

    
3324
		[self setDidUseCachedResponse:YES];
3325

    
3326
		[theRequest setResponseHeaders:headers];
3327
		if ([theRequest downloadDestinationPath]) {
3328
			[theRequest setDownloadDestinationPath:dataPath];
3329
		} else {
3330
			[theRequest setRawResponseData:[NSMutableData dataWithData:[[self downloadCache] cachedResponseDataForURL:[self url]]]];
3331
		}
3332
		[theRequest setContentLength:[[[self responseHeaders] objectForKey:@"Content-Length"] longLongValue]];
3333
		[theRequest setTotalBytesRead:[self contentLength]];
3334

    
3335
		[theRequest parseStringEncodingFromHeaders];
3336

    
3337
		[theRequest setResponseCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:[self url]]];
3338
	}
3339
	[theRequest setComplete:YES];
3340
	[theRequest setDownloadComplete:YES];
3341
	
3342
	// If we're pulling data from the cache without contacting the server at all, we won't have set originalURL yet
3343
	if ([self redirectCount] == 0) {
3344
		[theRequest setOriginalURL:[theRequest url]];
3345
	}
3346

    
3347
	[theRequest updateProgressIndicators];
3348
	[theRequest performSelectorOnMainThread:@selector(requestFinished) withObject:nil waitUntilDone:[NSThread isMainThread]];
3349
	[theRequest markAsFinished];	
3350
	if ([self mainRequest]) {
3351
		[self markAsFinished];
3352
	}
3353
}
3354

    
3355
- (BOOL)retryUsingNewConnection
3356
{
3357
    // commented out by mike mayo
3358
	//if ([self retryCount] == 0) {
3359
    if ([self retryCount] < 10) {
3360
		#if DEBUG_PERSISTENT_CONNECTIONS
3361
			NSLog(@"Request attempted to use connection #%@, but it has been closed - will retry with a new connection", [[self connectionInfo] objectForKey:@"id"]);
3362
		#endif
3363
		[connectionsLock lock];
3364
		[[self connectionInfo] removeObjectForKey:@"request"];
3365
		[persistentConnectionsPool removeObject:[self connectionInfo]];
3366
		[self setConnectionInfo:nil];
3367
		[connectionsLock unlock];
3368
		[self setRetryCount:[self retryCount]+1];
3369
		[self startRequest];
3370
		return YES;
3371
	}
3372
	#if DEBUG_PERSISTENT_CONNECTIONS
3373
		NSLog(@"Request attempted to use connection #%@, but it has been closed - we have already retried with a new connection, so we must give up", [[self connectionInfo] objectForKey:@"id"]);
3374
	#endif	
3375
	return NO;
3376
}
3377

    
3378
- (void)handleStreamError
3379

    
3380
{
3381
	NSError *underlyingError = NSMakeCollectable([(NSError *)CFReadStreamCopyError((CFReadStreamRef)[self readStream]) autorelease]);
3382

    
3383
	[self cancelLoad];
3384
	
3385
	if (![self error]) { // We may already have handled this error
3386
		
3387
		// First, check for a 'socket not connected', 'broken pipe' or 'connection lost' error
3388
		// This may occur when we've attempted to reuse a connection that should have been closed
3389
		// If we get this, we need to retry the request
3390
		// We'll only do this once - if it happens again on retry, we'll give up
3391
		// -1005 = kCFURLErrorNetworkConnectionLost - this doesn't seem to be declared on Mac OS 10.5
3392
		if (([[underlyingError domain] isEqualToString:NSPOSIXErrorDomain] && ([underlyingError code] == ENOTCONN || [underlyingError code] == EPIPE)) 
3393
			|| ([[underlyingError domain] isEqualToString:(NSString *)kCFErrorDomainCFNetwork] && [underlyingError code] == -1005)) {
3394
			if ([self retryUsingNewConnection]) {
3395
				return;
3396
			}
3397
		}
3398
		
3399
		NSString *reason = @"A connection failure occurred";
3400
		
3401
		// We'll use a custom error message for SSL errors, but you should always check underlying error if you want more details
3402
		// For some reason SecureTransport.h doesn't seem to be available on iphone, so error codes hard-coded
3403
		// Also, iPhone seems to handle errors differently from Mac OS X - a self-signed certificate returns a different error code on each platform, so we'll just provide a general error
3404
		if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) {
3405
			if ([underlyingError code] <= -9800 && [underlyingError code] >= -9818) {
3406
				reason = [NSString stringWithFormat:@"%@: SSL problem (possibly a bad/expired/self-signed certificate)",reason];
3407
			}
3408
		}
3409
		
3410
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]];
3411
	}
3412
	[self checkRequestStatus];
3413
}
3414

    
3415
#pragma mark managing the read stream
3416

    
3417
- (void)destroyReadStream
3418
{
3419
    if ([self readStream]) {
3420
		[self unscheduleReadStream];
3421
		if (![self connectionCanBeReused]) {
3422
			[[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3423
			[[self readStream] close];
3424
		}
3425
		[self setReadStream:nil];
3426
    }	
3427
}
3428

    
3429
- (void)scheduleReadStream
3430
{
3431
	if ([self readStream] && ![self readStreamIsScheduled]) {
3432

    
3433
		[connectionsLock lock];
3434
		runningRequestCount++;
3435
		if (shouldUpdateNetworkActivityIndicator) {
3436
			[[self class] showNetworkActivityIndicator];
3437
		}
3438
		[connectionsLock unlock];
3439

    
3440
		// Reset the timeout
3441
		[self setLastActivityTime:[NSDate date]];
3442
		CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
3443
		CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt);
3444
		[[self readStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3445
		[self setReadStreamIsScheduled:YES];
3446
	}
3447
}
3448

    
3449

    
3450
- (void)unscheduleReadStream
3451
{
3452
	if ([self readStream] && [self readStreamIsScheduled]) {
3453

    
3454
		[connectionsLock lock];
3455
		runningRequestCount--;
3456
		if (shouldUpdateNetworkActivityIndicator && runningRequestCount == 0) {
3457
			// This call will wait half a second before turning off the indicator
3458
			// This can prevent flicker when you have a single request finish and then immediately start another request
3459
			// We run this on the main thread because we have no guarantee this thread will have a runloop in 0.5 seconds time
3460
			// We don't bother the cancel this call if we start a new request, because we'll check if requests are running before we hide it
3461
			[[self class] performSelectorOnMainThread:@selector(hideNetworkActivityIndicatorAfterDelay) withObject:nil waitUntilDone:[NSThread isMainThread]];
3462
		}
3463
		[connectionsLock unlock];
3464

    
3465
		CFReadStreamSetClient((CFReadStreamRef)[self readStream], kCFStreamEventNone, NULL, NULL);
3466
		[[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3467
		[self setReadStreamIsScheduled:NO];
3468
	}
3469
}
3470

    
3471
#pragma mark cleanup
3472

    
3473
- (BOOL)removeTemporaryDownloadFile
3474
{
3475
	NSError *err = nil;
3476
	if ([self temporaryFileDownloadPath]) {
3477
		if (![[self class] removeFileAtPath:[self temporaryFileDownloadPath] error:&err]) {
3478
			[self failWithError:err];
3479
		}
3480
		[self setTemporaryFileDownloadPath:nil];
3481
	}
3482
	return (!err);
3483
}
3484

    
3485
- (BOOL)removeTemporaryUncompressedDownloadFile
3486
{
3487
	NSError *err = nil;
3488
	if ([self temporaryUncompressedDataDownloadPath]) {
3489
		if (![[self class] removeFileAtPath:[self temporaryUncompressedDataDownloadPath] error:&err]) {
3490
			[self failWithError:err];
3491
		}
3492
		[self setTemporaryUncompressedDataDownloadPath:nil];
3493
	}
3494
	return (!err);
3495
}
3496

    
3497
- (BOOL)removeTemporaryUploadFile
3498
{
3499
	NSError *err = nil;
3500
	if ([self postBodyFilePath]) {
3501
		if (![[self class] removeFileAtPath:[self postBodyFilePath] error:&err]) {
3502
			[self failWithError:err];
3503
		}
3504
		[self setPostBodyFilePath:nil];
3505
	}
3506
	return (!err);
3507
}
3508

    
3509
- (BOOL)removeTemporaryCompressedUploadFile
3510
{
3511
	NSError *err = nil;
3512
	if ([self compressedPostBodyFilePath]) {
3513
		if (![[self class] removeFileAtPath:[self compressedPostBodyFilePath] error:&err]) {
3514
			[self failWithError:err];
3515
		}
3516
		[self setCompressedPostBodyFilePath:nil];
3517
	}
3518
	return (!err);
3519
}
3520

    
3521
+ (BOOL)removeFileAtPath:(NSString *)path error:(NSError **)err
3522
{
3523
	if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
3524
		NSError *removeError = nil;
3525
		[[NSFileManager defaultManager] removeItemAtPath:path error:&removeError];
3526
		if (removeError) {
3527
			if (err) {
3528
				*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'",path],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]];
3529
			}
3530
			return NO;
3531
		}
3532
	}
3533
	return YES;
3534
}
3535

    
3536

    
3537

    
3538
#pragma mark persistent connections
3539

    
3540
- (NSNumber *)connectionID
3541
{
3542
	return [[self connectionInfo] objectForKey:@"id"];
3543
}
3544

    
3545
+ (void)expirePersistentConnections
3546
{
3547
	[connectionsLock lock];
3548
	NSUInteger i;
3549
	for (i=0; i<[persistentConnectionsPool count]; i++) {
3550
		NSDictionary *existingConnection = [persistentConnectionsPool objectAtIndex:i];
3551
		if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"expires"] timeIntervalSinceNow] <= 0) {
3552
#if DEBUG_PERSISTENT_CONNECTIONS
3553
			NSLog(@"Closing connection #%i because it has expired",[[existingConnection objectForKey:@"id"] intValue]);
3554
#endif
3555
			NSInputStream *stream = [existingConnection objectForKey:@"stream"];
3556
			if (stream) {
3557
				[stream close];
3558
			}
3559
			[persistentConnectionsPool removeObject:existingConnection];
3560
			i--;
3561
		}
3562
	}	
3563
	[connectionsLock unlock];
3564
}
3565

    
3566
#pragma mark NSCopying
3567

    
3568
- (id)copyWithZone:(NSZone *)zone
3569
{
3570
	// Don't forget - this will return a retained copy!
3571
	ASIHTTPRequest *newRequest = [[[self class] alloc] initWithURL:[self url]];
3572
	[newRequest setDelegate:[self delegate]];
3573
	[newRequest setRequestMethod:[self requestMethod]];
3574
	[newRequest setPostBody:[self postBody]];
3575
	[newRequest setShouldStreamPostDataFromDisk:[self shouldStreamPostDataFromDisk]];
3576
	[newRequest setPostBodyFilePath:[self postBodyFilePath]];
3577
	[newRequest setRequestHeaders:[[[self requestHeaders] mutableCopyWithZone:zone] autorelease]];
3578
	[newRequest setRequestCookies:[[[self requestCookies] mutableCopyWithZone:zone] autorelease]];
3579
	[newRequest setUseCookiePersistence:[self useCookiePersistence]];
3580
	[newRequest setUseKeychainPersistence:[self useKeychainPersistence]];
3581
	[newRequest setUseSessionPersistence:[self useSessionPersistence]];
3582
	[newRequest setAllowCompressedResponse:[self allowCompressedResponse]];
3583
	[newRequest setDownloadDestinationPath:[self downloadDestinationPath]];
3584
	[newRequest setTemporaryFileDownloadPath:[self temporaryFileDownloadPath]];
3585
	[newRequest setUsername:[self username]];
3586
	[newRequest setPassword:[self password]];
3587
	[newRequest setDomain:[self domain]];
3588
	[newRequest setProxyUsername:[self proxyUsername]];
3589
	[newRequest setProxyPassword:[self proxyPassword]];
3590
	[newRequest setProxyDomain:[self proxyDomain]];
3591
	[newRequest setProxyHost:[self proxyHost]];
3592
	[newRequest setProxyPort:[self proxyPort]];
3593
	[newRequest setProxyType:[self proxyType]];
3594
	[newRequest setUploadProgressDelegate:[self uploadProgressDelegate]];
3595
	[newRequest setDownloadProgressDelegate:[self downloadProgressDelegate]];
3596
	[newRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]];
3597
	[newRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]];
3598
	[newRequest setPostLength:[self postLength]];
3599
	[newRequest setHaveBuiltPostBody:[self haveBuiltPostBody]];
3600
	[newRequest setDidStartSelector:[self didStartSelector]];
3601
	[newRequest setDidFinishSelector:[self didFinishSelector]];
3602
	[newRequest setDidFailSelector:[self didFailSelector]];
3603
	[newRequest setTimeOutSeconds:[self timeOutSeconds]];
3604
	[newRequest setShouldResetDownloadProgress:[self shouldResetDownloadProgress]];
3605
	[newRequest setShouldResetUploadProgress:[self shouldResetUploadProgress]];
3606
	[newRequest setShowAccurateProgress:[self showAccurateProgress]];
3607
	[newRequest setDefaultResponseEncoding:[self defaultResponseEncoding]];
3608
	[newRequest setAllowResumeForFileDownloads:[self allowResumeForFileDownloads]];
3609
	[newRequest setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]];
3610
	[newRequest setUseHTTPVersionOne:[self useHTTPVersionOne]];
3611
	[newRequest setShouldRedirect:[self shouldRedirect]];
3612
	[newRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
3613
    [newRequest setClientCertificateIdentity:clientCertificateIdentity];
3614
	[newRequest setClientCertificates:[[clientCertificates copy] autorelease]];
3615
	[newRequest setPACurl:[self PACurl]];
3616
	[newRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
3617
	[newRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]];
3618
	[newRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]];
3619
	[newRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]];
3620
	[newRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]];
3621
	return newRequest;
3622
}
3623

    
3624
#pragma mark default time out
3625

    
3626
+ (NSTimeInterval)defaultTimeOutSeconds
3627
{
3628
	return defaultTimeOutSeconds;
3629
}
3630

    
3631
+ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds
3632
{
3633
	defaultTimeOutSeconds = newTimeOutSeconds;
3634
}
3635

    
3636

    
3637
#pragma mark client certificate
3638

    
3639
- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity {
3640
    if(clientCertificateIdentity) {
3641
        CFRelease(clientCertificateIdentity);
3642
    }
3643
    
3644
    clientCertificateIdentity = anIdentity;
3645
    
3646
	if (clientCertificateIdentity) {
3647
		CFRetain(clientCertificateIdentity);
3648
	}
3649
}
3650

    
3651

    
3652
#pragma mark session credentials
3653

    
3654
+ (NSMutableArray *)sessionProxyCredentialsStore
3655
{
3656
	[sessionCredentialsLock lock];
3657
	if (!sessionProxyCredentialsStore) {
3658
		sessionProxyCredentialsStore = [[NSMutableArray alloc] init];
3659
	}
3660
	[sessionCredentialsLock unlock];
3661
	return sessionProxyCredentialsStore;
3662
}
3663

    
3664
+ (NSMutableArray *)sessionCredentialsStore
3665
{
3666
	[sessionCredentialsLock lock];
3667
	if (!sessionCredentialsStore) {
3668
		sessionCredentialsStore = [[NSMutableArray alloc] init];
3669
	}
3670
	[sessionCredentialsLock unlock];
3671
	return sessionCredentialsStore;
3672
}
3673

    
3674
+ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
3675
{
3676
	[sessionCredentialsLock lock];
3677
	[self removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
3678
	[[[self class] sessionProxyCredentialsStore] addObject:credentials];
3679
	[sessionCredentialsLock unlock];
3680
}
3681

    
3682
+ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
3683
{
3684
	[sessionCredentialsLock lock];
3685
	[self removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
3686
	[[[self class] sessionCredentialsStore] addObject:credentials];
3687
	[sessionCredentialsLock unlock];
3688
}
3689

    
3690
+ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
3691
{
3692
	[sessionCredentialsLock lock];
3693
	NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
3694
	NSUInteger i;
3695
	for (i=0; i<[sessionCredentialsList count]; i++) {
3696
		NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
3697
		if ([theCredentials objectForKey:@"Credentials"] == credentials) {
3698
			[sessionCredentialsList removeObjectAtIndex:i];
3699
			[sessionCredentialsLock unlock];
3700
			return;
3701
		}
3702
	}
3703
	[sessionCredentialsLock unlock];
3704
}
3705

    
3706
+ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
3707
{
3708
	[sessionCredentialsLock lock];
3709
	NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
3710
	NSUInteger i;
3711
	for (i=0; i<[sessionCredentialsList count]; i++) {
3712
		NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
3713
		if ([theCredentials objectForKey:@"Credentials"] == credentials) {
3714
			[sessionCredentialsList removeObjectAtIndex:i];
3715
			[sessionCredentialsLock unlock];
3716
			return;
3717
		}
3718
	}
3719
	[sessionCredentialsLock unlock];
3720
}
3721

    
3722
- (NSDictionary *)findSessionProxyAuthenticationCredentials
3723
{
3724
	[sessionCredentialsLock lock];
3725
	NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
3726
	for (NSDictionary *theCredentials in sessionCredentialsList) {
3727
		if ([[theCredentials objectForKey:@"Host"] isEqualToString:[self proxyHost]] && [[theCredentials objectForKey:@"Port"] intValue] == [self proxyPort]) {
3728
			[sessionCredentialsLock unlock];
3729
			return theCredentials;
3730
		}
3731
	}
3732
	[sessionCredentialsLock unlock];
3733
	return nil;
3734
}
3735

    
3736

    
3737
- (NSDictionary *)findSessionAuthenticationCredentials
3738
{
3739
	[sessionCredentialsLock lock];
3740
	NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
3741
	// Find an exact match (same url)
3742
	for (NSDictionary *theCredentials in sessionCredentialsList) {
3743
		if ([(NSURL*)[theCredentials objectForKey:@"URL"] isEqual:[self url]]) {
3744
			// /Just a sanity check to ensure we never choose credentials from a different realm. Can't really do more than that, as either this request or the stored credentials may not have a realm when the other does
3745
			if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
3746
				[sessionCredentialsLock unlock];
3747
				return theCredentials;
3748
			}
3749
		}
3750
	}
3751
	// Find a rough match (same host, port, scheme)
3752
	NSURL *requestURL = [self url];
3753
	for (NSDictionary *theCredentials in sessionCredentialsList) {
3754
		NSURL *theURL = [theCredentials objectForKey:@"URL"];
3755
		
3756
		// Port can be nil!
3757
		if ([[theURL host] isEqualToString:[requestURL host]] && ([theURL port] == [requestURL port] || ([requestURL port] && [[theURL port] isEqualToNumber:[requestURL port]])) && [[theURL scheme] isEqualToString:[requestURL scheme]]) {
3758
			if (![self responseStatusCode] || (![theCredentials objectForKey:@"AuthenticationRealm"] || [[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
3759
				[sessionCredentialsLock unlock];
3760
				return theCredentials;
3761
			}
3762
		}
3763
	}
3764
	[sessionCredentialsLock unlock];
3765
	return nil;
3766
}
3767

    
3768
#pragma mark keychain storage
3769

    
3770
+ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
3771
{
3772
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
3773
	[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
3774
}
3775

    
3776
+ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm
3777
{
3778
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
3779
	[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
3780
}
3781

    
3782
+ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
3783
{
3784
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
3785
	return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
3786
}
3787

    
3788
+ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
3789
{
3790
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
3791
	return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
3792
}
3793

    
3794
+ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
3795
{
3796
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
3797
	NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
3798
	if (credential) {
3799
		[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace];
3800
	}
3801
}
3802

    
3803
+ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm
3804
{
3805
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
3806
	NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
3807
	if (credential) {
3808
		[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace];
3809
	}
3810
}
3811

    
3812

    
3813
+ (NSMutableArray *)sessionCookies
3814
{
3815
	if (!sessionCookies) {
3816
		[ASIHTTPRequest setSessionCookies:[[[NSMutableArray alloc] init] autorelease]];
3817
	}
3818
	return sessionCookies;
3819
}
3820

    
3821
+ (void)setSessionCookies:(NSMutableArray *)newSessionCookies
3822
{
3823
	[sessionCookiesLock lock];
3824
	// Remove existing cookies from the persistent store
3825
	for (NSHTTPCookie *cookie in sessionCookies) {
3826
		[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
3827
	}
3828
	[sessionCookies release];
3829
	sessionCookies = [newSessionCookies retain];
3830
	[sessionCookiesLock unlock];
3831
}
3832

    
3833
+ (void)addSessionCookie:(NSHTTPCookie *)newCookie
3834
{
3835
	[sessionCookiesLock lock];
3836
	NSHTTPCookie *cookie;
3837
	NSUInteger i;
3838
	NSUInteger max = [[ASIHTTPRequest sessionCookies] count];
3839
	for (i=0; i<max; i++) {
3840
		cookie = [[ASIHTTPRequest sessionCookies] objectAtIndex:i];
3841
		if ([[cookie domain] isEqualToString:[newCookie domain]] && [[cookie path] isEqualToString:[newCookie path]] && [[cookie name] isEqualToString:[newCookie name]]) {
3842
			[[ASIHTTPRequest sessionCookies] removeObjectAtIndex:i];
3843
			break;
3844
		}
3845
	}
3846
	[[ASIHTTPRequest sessionCookies] addObject:newCookie];
3847
	[sessionCookiesLock unlock];
3848
}
3849

    
3850
// Dump all session data (authentication and cookies)
3851
+ (void)clearSession
3852
{
3853
	[sessionCredentialsLock lock];
3854
	[[[self class] sessionCredentialsStore] removeAllObjects];
3855
	[sessionCredentialsLock unlock];
3856
	[[self class] setSessionCookies:nil];
3857
	[[[self class] defaultCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
3858
}
3859

    
3860
#pragma mark get user agent
3861

    
3862
+ (NSString *)defaultUserAgentString
3863
{
3864
	NSBundle *bundle = [NSBundle bundleForClass:[self class]];
3865

    
3866
	// Attempt to find a name for this application
3867
	NSString *appName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
3868
	if (!appName) {
3869
		appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"];	
3870
	}
3871
	// If we couldn't find one, we'll give up (and ASIHTTPRequest will use the standard CFNetwork user agent)
3872
	if (!appName) {
3873
		return nil;
3874
	}
3875
	NSString *appVersion = nil;
3876
	NSString *marketingVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
3877
    NSString *developmentVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
3878
	if (marketingVersionNumber && developmentVersionNumber) {
3879
		if ([marketingVersionNumber isEqualToString:developmentVersionNumber]) {
3880
			appVersion = marketingVersionNumber;
3881
		} else {
3882
			appVersion = [NSString stringWithFormat:@"%@ rv:%@",marketingVersionNumber,developmentVersionNumber];
3883
		}
3884
	} else {
3885
		appVersion = (marketingVersionNumber ? marketingVersionNumber : developmentVersionNumber);
3886
	}
3887
	
3888
	
3889
	NSString *deviceName;
3890
	NSString *OSName;
3891
	NSString *OSVersion;
3892
	
3893
	NSString *locale = [[NSLocale currentLocale] localeIdentifier];
3894
	
3895
#if TARGET_OS_IPHONE
3896
	UIDevice *device = [UIDevice currentDevice];
3897
	deviceName = [device model];
3898
	OSName = [device systemName];
3899
	OSVersion = [device systemVersion];
3900
	
3901
#else
3902
	deviceName = @"Macintosh";
3903
	OSName = @"Mac OS X";
3904
	
3905
	// From http://www.cocoadev.com/index.pl?DeterminingOSVersion
3906
	// We won't bother to check for systems prior to 10.4, since ASIHTTPRequest only works on 10.5+
3907
    OSErr err;
3908
    SInt32 versionMajor, versionMinor, versionBugFix;
3909
	err = Gestalt(gestaltSystemVersionMajor, &versionMajor);
3910
	if (err != noErr) return nil;
3911
	err = Gestalt(gestaltSystemVersionMinor, &versionMinor);
3912
	if (err != noErr) return nil;
3913
	err = Gestalt(gestaltSystemVersionBugFix, &versionBugFix);
3914
	if (err != noErr) return nil;
3915
	OSVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix];
3916
	
3917
#endif
3918
	// Takes the form "My Application 1.0 (Macintosh; Mac OS X 10.5.7; en_GB)"
3919
	return [NSString stringWithFormat:@"%@ %@ (%@; %@ %@; %@)", appName, appVersion, deviceName, OSName, OSVersion, locale];
3920
}
3921

    
3922
#pragma mark proxy autoconfiguration
3923

    
3924
// Returns an array of proxies to use for a particular url, given the url of a PAC script
3925
+ (NSArray *)proxiesForURL:(NSURL *)theURL fromPAC:(NSURL *)pacScriptURL
3926
{
3927
	// From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
3928
	// Work around <rdar://problem/5530166>.  This dummy call to 
3929
	// CFNetworkCopyProxiesForURL initialise some state within CFNetwork 
3930
	// that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
3931
	CFRelease(CFNetworkCopyProxiesForURL((CFURLRef)theURL, NULL));
3932
	
3933
	NSStringEncoding encoding;
3934
	NSError *err = nil;
3935
	NSString *script = [NSString stringWithContentsOfURL:pacScriptURL usedEncoding:&encoding error:&err];
3936
	if (err) {
3937
		// If we can't fetch the PAC, we'll assume no proxies
3938
		// Some people have a PAC configured that is not always available, so I think this is the best behaviour
3939
		return [NSArray array];
3940
	}
3941
	// Obtain the list of proxies by running the autoconfiguration script
3942
	CFErrorRef err2 = NULL;
3943
	NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)theURL, &err2) autorelease]);
3944
	if (err2) {
3945
		return nil;
3946
	}
3947
	return proxies;
3948
}
3949

    
3950
#pragma mark mime-type detection
3951

    
3952
+ (NSString *)mimeTypeForFileAtPath:(NSString *)path
3953
{
3954
	if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
3955
		return nil;
3956
	}
3957
	// Borrowed from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
3958
	CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[path pathExtension], NULL);
3959
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
3960
    CFRelease(UTI);
3961
	if (!MIMEType) {
3962
		return @"application/octet-stream";
3963
	}
3964
    return NSMakeCollectable([(NSString *)MIMEType autorelease]);
3965
}
3966

    
3967
#pragma mark bandwidth measurement / throttling
3968

    
3969
- (void)performThrottling
3970
{
3971
	if (![self readStream]) {
3972
		return;
3973
	}
3974
	[ASIHTTPRequest measureBandwidthUsage];
3975
	if ([ASIHTTPRequest isBandwidthThrottled]) {
3976
		[bandwidthThrottlingLock lock];
3977
		// Handle throttling
3978
		if (throttleWakeUpTime) {
3979
			if ([throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] > 0) {
3980
				if ([self readStreamIsScheduled]) {
3981
					[self unscheduleReadStream];
3982
					#if DEBUG_THROTTLING
3983
					NSLog(@"Sleeping request %@ until after %@",self,throttleWakeUpTime);
3984
					#endif
3985
				}
3986
			} else {
3987
				if (![self readStreamIsScheduled]) {
3988
					[self scheduleReadStream];
3989
					#if DEBUG_THROTTLING
3990
					NSLog(@"Waking up request %@",self);
3991
					#endif
3992
				}
3993
			}
3994
		} 
3995
		[bandwidthThrottlingLock unlock];
3996
		
3997
	// Bandwidth throttling must have been turned off since we last looked, let's re-schedule the stream
3998
	} else if (![self readStreamIsScheduled]) {
3999
		[self scheduleReadStream];			
4000
	}
4001
}
4002

    
4003
+ (BOOL)isBandwidthThrottled
4004
{
4005
#if TARGET_OS_IPHONE
4006
	[bandwidthThrottlingLock lock];
4007

    
4008
	BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond));
4009
	[bandwidthThrottlingLock unlock];
4010
	return throttle;
4011
#else
4012
	[bandwidthThrottlingLock lock];
4013
	BOOL throttle = (maxBandwidthPerSecond);
4014
	[bandwidthThrottlingLock unlock];
4015
	return throttle;
4016
#endif
4017
}
4018

    
4019
+ (unsigned long)maxBandwidthPerSecond
4020
{
4021
	[bandwidthThrottlingLock lock];
4022
	unsigned long amount = maxBandwidthPerSecond;
4023
	[bandwidthThrottlingLock unlock];
4024
	return amount;
4025
}
4026

    
4027
+ (void)setMaxBandwidthPerSecond:(unsigned long)bytes
4028
{
4029
	[bandwidthThrottlingLock lock];
4030
	maxBandwidthPerSecond = bytes;
4031
	[bandwidthThrottlingLock unlock];
4032
}
4033

    
4034
+ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes
4035
{
4036
	[bandwidthThrottlingLock lock];
4037
	bandwidthUsedInLastSecond += bytes;
4038
	[bandwidthThrottlingLock unlock];
4039
}
4040

    
4041
+ (void)recordBandwidthUsage
4042
{
4043
	if (bandwidthUsedInLastSecond == 0) {
4044
		[bandwidthUsageTracker removeAllObjects];
4045
	} else {
4046
		NSTimeInterval interval = [bandwidthMeasurementDate timeIntervalSinceNow];
4047
		while ((interval < 0 || [bandwidthUsageTracker count] > 5) && [bandwidthUsageTracker count] > 0) {
4048
			[bandwidthUsageTracker removeObjectAtIndex:0];
4049
			interval++;
4050
		}
4051
	}
4052
	#if DEBUG_THROTTLING
4053
	NSLog(@"===Used: %u bytes of bandwidth in last measurement period===",bandwidthUsedInLastSecond);
4054
	#endif
4055
	[bandwidthUsageTracker addObject:[NSNumber numberWithUnsignedLong:bandwidthUsedInLastSecond]];
4056
	[bandwidthMeasurementDate release];
4057
	bandwidthMeasurementDate = [[NSDate dateWithTimeIntervalSinceNow:1] retain];
4058
	bandwidthUsedInLastSecond = 0;
4059
	
4060
	NSUInteger measurements = [bandwidthUsageTracker count];
4061
	unsigned long totalBytes = 0;
4062
	for (NSNumber *bytes in bandwidthUsageTracker) {
4063
		totalBytes += [bytes unsignedLongValue];
4064
	}
4065
	averageBandwidthUsedPerSecond = totalBytes/measurements;		
4066
}
4067

    
4068
+ (unsigned long)averageBandwidthUsedPerSecond
4069
{
4070
	[bandwidthThrottlingLock lock];
4071
	unsigned long amount = 	averageBandwidthUsedPerSecond;
4072
	[bandwidthThrottlingLock unlock];
4073
	return amount;
4074
}
4075

    
4076
+ (void)measureBandwidthUsage
4077
{
4078
	// Other requests may have to wait for this lock if we're sleeping, but this is fine, since in that case we already know they shouldn't be sending or receiving data
4079
	[bandwidthThrottlingLock lock];
4080

    
4081
	if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
4082
		[ASIHTTPRequest recordBandwidthUsage];
4083
	}
4084
	
4085
	// Are we performing bandwidth throttling?
4086
	if (
4087
	#if TARGET_OS_IPHONE
4088
	isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond))
4089
	#else
4090
	maxBandwidthPerSecond
4091
	#endif
4092
	) {
4093
		// How much data can we still send or receive this second?
4094
		long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond;
4095
			
4096
		// Have we used up our allowance?
4097
		if (bytesRemaining < 0) {
4098
			
4099
			// Yes, put this request to sleep until a second is up, with extra added punishment sleeping time for being very naughty (we have used more bandwidth than we were allowed)
4100
			double extraSleepyTime = (-bytesRemaining/(maxBandwidthPerSecond*1.0));
4101
			[throttleWakeUpTime release];
4102
			throttleWakeUpTime = [[NSDate alloc] initWithTimeInterval:extraSleepyTime sinceDate:bandwidthMeasurementDate];
4103
		}
4104
	}
4105
	[bandwidthThrottlingLock unlock];
4106
}
4107
	
4108
+ (unsigned long)maxUploadReadLength
4109
{
4110
	
4111
	[bandwidthThrottlingLock lock];
4112
	
4113
	// We'll split our bandwidth allowance into 4 (which is the default for an ASINetworkQueue's max concurrent operations count) to give all running requests a fighting chance of reading data this cycle
4114
	long long toRead = maxBandwidthPerSecond/4;
4115
	if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) {
4116
		toRead = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
4117
		if (toRead < 0) {
4118
			toRead = 0;
4119
		}
4120
	}
4121
	
4122
	if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
4123
		[throttleWakeUpTime release];
4124
		throttleWakeUpTime = [bandwidthMeasurementDate retain];
4125
	}
4126
	[bandwidthThrottlingLock unlock];	
4127
	return (unsigned long)toRead;
4128
}
4129
	
4130

    
4131
#if TARGET_OS_IPHONE
4132
+ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle
4133
{
4134
	if (throttle) {
4135
		[ASIHTTPRequest throttleBandwidthForWWANUsingLimit:ASIWWANBandwidthThrottleAmount];
4136
	} else {
4137
		[ASIHTTPRequest unsubscribeFromNetworkReachabilityNotifications];
4138
		[ASIHTTPRequest setMaxBandwidthPerSecond:0];
4139
		[bandwidthThrottlingLock lock];
4140
		isBandwidthThrottled = NO;
4141
		shouldThrottleBandwithForWWANOnly = NO;
4142
		[bandwidthThrottlingLock unlock];
4143
	}
4144
}
4145

    
4146
+ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit
4147
{	
4148
	[bandwidthThrottlingLock lock];
4149
	shouldThrottleBandwithForWWANOnly = YES;
4150
	maxBandwidthPerSecond = limit;
4151
	[ASIHTTPRequest registerForNetworkReachabilityNotifications];	
4152
	[bandwidthThrottlingLock unlock];
4153
	[ASIHTTPRequest reachabilityChanged:nil];
4154
}
4155

    
4156
#pragma mark reachability
4157

    
4158
+ (void)registerForNetworkReachabilityNotifications
4159
{
4160
	[[Reachability reachabilityForInternetConnection] startNotifier];
4161
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
4162
}
4163

    
4164

    
4165
+ (void)unsubscribeFromNetworkReachabilityNotifications
4166
{
4167
	[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
4168
}
4169

    
4170
+ (BOOL)isNetworkReachableViaWWAN
4171
{
4172
	return ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == ReachableViaWWAN);	
4173
}
4174

    
4175
+ (void)reachabilityChanged:(NSNotification *)note
4176
{
4177
	[bandwidthThrottlingLock lock];
4178
	isBandwidthThrottled = [ASIHTTPRequest isNetworkReachableViaWWAN];
4179
	[bandwidthThrottlingLock unlock];
4180
}
4181
#endif
4182

    
4183
#pragma mark queue
4184

    
4185
// Returns the shared queue
4186
+ (NSOperationQueue *)sharedQueue
4187
{
4188
    return [[sharedQueue retain] autorelease];
4189
}
4190

    
4191
#pragma mark cache
4192

    
4193
+ (void)setDefaultCache:(id <ASICacheDelegate>)cache
4194
{
4195
	[defaultCache release];
4196
	defaultCache = [cache retain];
4197
}
4198

    
4199
+ (id <ASICacheDelegate>)defaultCache
4200
{
4201
	return defaultCache;
4202
}
4203

    
4204

    
4205
#pragma mark network activity
4206

    
4207
+ (BOOL)isNetworkInUse
4208
{
4209
	[connectionsLock lock];
4210
	BOOL inUse = (runningRequestCount > 0);
4211
	[connectionsLock unlock];
4212
	return inUse;
4213
}
4214

    
4215
+ (void)setShouldUpdateNetworkActivityIndicator:(BOOL)shouldUpdate
4216
{
4217
	[connectionsLock lock];
4218
	shouldUpdateNetworkActivityIndicator = shouldUpdate;
4219
	[connectionsLock unlock];
4220
}
4221

    
4222
+ (void)showNetworkActivityIndicator
4223
{
4224
#if TARGET_OS_IPHONE
4225
	[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
4226
#endif
4227
}
4228

    
4229
+ (void)hideNetworkActivityIndicator
4230
{
4231
#if TARGET_OS_IPHONE
4232
	[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];	
4233
#endif
4234
}
4235

    
4236

    
4237
/* Always called on main thread */
4238
+ (void)hideNetworkActivityIndicatorAfterDelay
4239
{
4240
	[self performSelector:@selector(hideNetworkActivityIndicatorIfNeeeded) withObject:nil afterDelay:0.5];
4241
}
4242

    
4243
+ (void)hideNetworkActivityIndicatorIfNeeeded
4244
{
4245
	[connectionsLock lock];
4246
	if (runningRequestCount == 0) {
4247
		[self hideNetworkActivityIndicator];
4248
	}
4249
	[connectionsLock unlock];
4250
}
4251

    
4252

    
4253
#pragma mark threading behaviour
4254

    
4255
// In the default implementation, all requests run in a single background thread
4256
// Advanced users only: Override this method in a subclass for a different threading behaviour
4257
// Eg: return [NSThread mainThread] to run all requests in the main thread
4258
// Alternatively, you can create a thread on demand, or manage a pool of threads
4259
// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun())
4260
// Requests will stop the runloop when they complete
4261
// If you have multiple requests sharing the thread or you want to re-use the thread, you'll need to restart the runloop
4262
+ (NSThread *)threadForRequest:(ASIHTTPRequest *)request
4263
{
4264
	if (!networkThread) {
4265
		networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil];
4266
		[networkThread start];
4267
	}
4268
	return networkThread;
4269
}
4270

    
4271
+ (void)runRequests
4272
{
4273
	// Should keep the runloop from exiting
4274
	CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4275
	CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
4276
	CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
4277

    
4278
    BOOL runAlways = YES; // Introduced to cheat Static Analyzer
4279
	while (runAlways) {
4280
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
4281
		CFRunLoopRun();
4282
		[pool release];
4283
	}
4284

    
4285
	// Should never be called, but anyway
4286
	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
4287
	CFRelease(source);
4288
}
4289

    
4290
#pragma mark miscellany 
4291

    
4292
#if TARGET_OS_IPHONE
4293
+ (BOOL)isMultitaskingSupported
4294
{
4295
	BOOL multiTaskingSupported = NO;
4296
	if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
4297
		multiTaskingSupported = [(id)[UIDevice currentDevice] isMultitaskingSupported];
4298
	}
4299
	return multiTaskingSupported;
4300
}
4301
#endif
4302

    
4303
// From: http://www.cocoadev.com/index.pl?BaseSixtyFour
4304

    
4305
+ (NSString*)base64forData:(NSData*)theData {
4306
	
4307
	const uint8_t* input = (const uint8_t*)[theData bytes];
4308
	NSInteger length = [theData length];
4309
	
4310
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
4311
	
4312
    NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
4313
    uint8_t* output = (uint8_t*)data.mutableBytes;
4314
	
4315
	NSInteger i;
4316
    for (i=0; i < length; i += 3) {
4317
        NSInteger value = 0;
4318
		NSInteger j;
4319
        for (j = i; j < (i + 3); j++) {
4320
            value <<= 8;
4321
			
4322
            if (j < length) {
4323
                value |= (0xFF & input[j]);
4324
            }
4325
        }
4326
		
4327
        NSInteger theIndex = (i / 3) * 4;
4328
        output[theIndex + 0] =                    table[(value >> 18) & 0x3F];
4329
        output[theIndex + 1] =                    table[(value >> 12) & 0x3F];
4330
        output[theIndex + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';
4331
        output[theIndex + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';
4332
    }
4333
	
4334
    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
4335
}
4336

    
4337
// Based on hints from http://stackoverflow.com/questions/1850824/parsing-a-rfc-822-date-with-nsdateformatter
4338
+ (NSDate *)dateFromRFC1123String:(NSString *)string
4339
{
4340
	NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
4341
	[formatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
4342
	// Does the string include a week day?
4343
	NSString *day = @"";
4344
	if ([string rangeOfString:@","].location != NSNotFound) {
4345
		day = @"EEE, ";
4346
	}
4347
	// Does the string include seconds?
4348
	NSString *seconds = @"";
4349
	if ([[string componentsSeparatedByString:@":"] count] == 3) {
4350
		seconds = @":ss";
4351
	}
4352
	[formatter setDateFormat:[NSString stringWithFormat:@"%@dd MMM yyyy HH:mm%@ z",day,seconds]];
4353
	return [formatter dateFromString:string];
4354
}
4355

    
4356
+ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType
4357
{
4358
	if (!contentType) {
4359
		return;
4360
	}
4361
	NSScanner *charsetScanner = [NSScanner scannerWithString: contentType];
4362
	if (![charsetScanner scanUpToString:@";" intoString:mimeType] || [charsetScanner scanLocation] == [contentType length]) {
4363
		*mimeType = [contentType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
4364
		return;
4365
	}
4366
	*mimeType = [*mimeType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
4367
	NSString *charsetSeparator = @"charset=";
4368
	NSString *IANAEncoding = nil;
4369

    
4370
	if ([charsetScanner scanUpToString: charsetSeparator intoString: NULL] && [charsetScanner scanLocation] < [contentType length]) {
4371
		[charsetScanner setScanLocation: [charsetScanner scanLocation] + [charsetSeparator length]];
4372
		[charsetScanner scanUpToString: @";" intoString: &IANAEncoding];
4373
	}
4374

    
4375
	if (IANAEncoding) {
4376
		CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding);
4377
		if (cfEncoding != kCFStringEncodingInvalidId) {
4378
			*stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
4379
		}
4380
	}
4381
}
4382

    
4383
#pragma mark -
4384
#pragma mark blocks
4385
#if NS_BLOCKS_AVAILABLE
4386
- (void)setStartedBlock:(ASIBasicBlock)aStartedBlock
4387
{
4388
	[startedBlock release];
4389
	startedBlock = [aStartedBlock copy];
4390
}
4391

    
4392
- (void)setHeadersReceivedBlock:(ASIHeadersBlock)aReceivedBlock
4393
{
4394
	[headersReceivedBlock release];
4395
	headersReceivedBlock = [aReceivedBlock copy];
4396
}
4397

    
4398
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock
4399
{
4400
	[completionBlock release];
4401
	completionBlock = [aCompletionBlock copy];
4402
}
4403

    
4404
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock
4405
{
4406
	[failureBlock release];
4407
	failureBlock = [aFailedBlock copy];
4408
}
4409

    
4410
- (void)setBytesReceivedBlock:(ASIProgressBlock)aBytesReceivedBlock
4411
{
4412
	[bytesReceivedBlock release];
4413
	bytesReceivedBlock = [aBytesReceivedBlock copy];
4414
}
4415

    
4416
- (void)setBytesSentBlock:(ASIProgressBlock)aBytesSentBlock
4417
{
4418
	[bytesSentBlock release];
4419
	bytesSentBlock = [aBytesSentBlock copy];
4420
}
4421

    
4422
- (void)setDownloadSizeIncrementedBlock:(ASISizeBlock)aDownloadSizeIncrementedBlock{
4423
	[downloadSizeIncrementedBlock release];
4424
	downloadSizeIncrementedBlock = [aDownloadSizeIncrementedBlock copy];
4425
}
4426

    
4427
- (void)setUploadSizeIncrementedBlock:(ASISizeBlock)anUploadSizeIncrementedBlock
4428
{
4429
	[uploadSizeIncrementedBlock release];
4430
	uploadSizeIncrementedBlock = [anUploadSizeIncrementedBlock copy];
4431
}
4432

    
4433
- (void)setDataReceivedBlock:(ASIDataBlock)aReceivedBlock
4434
{
4435
	[dataReceivedBlock release];
4436
	dataReceivedBlock = [aReceivedBlock copy];
4437
}
4438

    
4439
- (void)setAuthenticationNeededBlock:(ASIBasicBlock)anAuthenticationBlock
4440
{
4441
	[authenticationNeededBlock release];
4442
	authenticationNeededBlock = [anAuthenticationBlock copy];
4443
}
4444
- (void)setProxyAuthenticationNeededBlock:(ASIBasicBlock)aProxyAuthenticationBlock
4445
{
4446
	[proxyAuthenticationNeededBlock release];
4447
	proxyAuthenticationNeededBlock = [aProxyAuthenticationBlock copy];
4448
}
4449
- (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock
4450
{
4451
	[requestRedirectedBlock release];
4452
	requestRedirectedBlock = [aRedirectBlock copy];
4453
}
4454
#endif
4455

    
4456
#pragma mark ===
4457

    
4458
@synthesize username;
4459
@synthesize password;
4460
@synthesize domain;
4461
@synthesize proxyUsername;
4462
@synthesize proxyPassword;
4463
@synthesize proxyDomain;
4464
@synthesize url;
4465
@synthesize originalURL;
4466
@synthesize delegate;
4467
@synthesize queue;
4468
@synthesize uploadProgressDelegate;
4469
@synthesize downloadProgressDelegate;
4470
@synthesize useKeychainPersistence;
4471
@synthesize useSessionPersistence;
4472
@synthesize useCookiePersistence;
4473
@synthesize downloadDestinationPath;
4474
@synthesize temporaryFileDownloadPath;
4475
@synthesize temporaryUncompressedDataDownloadPath;
4476
@synthesize didStartSelector;
4477
@synthesize didReceiveResponseHeadersSelector;
4478
@synthesize willRedirectSelector;
4479
@synthesize didFinishSelector;
4480
@synthesize didFailSelector;
4481
@synthesize didReceiveDataSelector;
4482
@synthesize authenticationRealm;
4483
@synthesize proxyAuthenticationRealm;
4484
@synthesize error;
4485
@synthesize complete;
4486
@synthesize requestHeaders;
4487
@synthesize responseHeaders;
4488
@synthesize responseCookies;
4489
@synthesize requestCookies;
4490
@synthesize requestCredentials;
4491
@synthesize responseStatusCode;
4492
@synthesize rawResponseData;
4493
@synthesize lastActivityTime;
4494
@synthesize timeOutSeconds;
4495
@synthesize requestMethod;
4496
@synthesize postBody;
4497
@synthesize compressedPostBody;
4498
@synthesize contentLength;
4499
@synthesize partialDownloadSize;
4500
@synthesize postLength;
4501
@synthesize shouldResetDownloadProgress;
4502
@synthesize shouldResetUploadProgress;
4503
@synthesize mainRequest;
4504
@synthesize totalBytesRead;
4505
@synthesize totalBytesSent;
4506
@synthesize showAccurateProgress;
4507
@synthesize uploadBufferSize;
4508
@synthesize defaultResponseEncoding;
4509
@synthesize responseEncoding;
4510
@synthesize allowCompressedResponse;
4511
@synthesize allowResumeForFileDownloads;
4512
@synthesize userInfo;
4513
@synthesize postBodyFilePath;
4514
@synthesize compressedPostBodyFilePath;
4515
@synthesize postBodyWriteStream;
4516
@synthesize postBodyReadStream;
4517
@synthesize shouldStreamPostDataFromDisk;
4518
@synthesize didCreateTemporaryPostDataFile;
4519
@synthesize useHTTPVersionOne;
4520
@synthesize lastBytesRead;
4521
@synthesize lastBytesSent;
4522
@synthesize cancelledLock;
4523
@synthesize haveBuiltPostBody;
4524
@synthesize fileDownloadOutputStream;
4525
@synthesize inflatedFileDownloadOutputStream;
4526
@synthesize authenticationRetryCount;
4527
@synthesize proxyAuthenticationRetryCount;
4528
@synthesize updatedProgress;
4529
@synthesize shouldRedirect;
4530
@synthesize validatesSecureCertificate;
4531
@synthesize needsRedirect;
4532
@synthesize redirectCount;
4533
@synthesize shouldCompressRequestBody;
4534
@synthesize proxyCredentials;
4535
@synthesize proxyHost;
4536
@synthesize proxyPort;
4537
@synthesize proxyType;
4538
@synthesize PACurl;
4539
@synthesize authenticationScheme;
4540
@synthesize proxyAuthenticationScheme;
4541
@synthesize shouldPresentAuthenticationDialog;
4542
@synthesize shouldPresentProxyAuthenticationDialog;
4543
@synthesize authenticationNeeded;
4544
@synthesize responseStatusMessage;
4545
@synthesize shouldPresentCredentialsBeforeChallenge;
4546
@synthesize haveBuiltRequestHeaders;
4547
@synthesize inProgress;
4548
@synthesize numberOfTimesToRetryOnTimeout;
4549
@synthesize retryCount;
4550
@synthesize shouldAttemptPersistentConnection;
4551
@synthesize persistentConnectionTimeoutSeconds;
4552
@synthesize connectionCanBeReused;
4553
@synthesize connectionInfo;
4554
@synthesize readStream;
4555
@synthesize readStreamIsScheduled;
4556
@synthesize shouldUseRFC2616RedirectBehaviour;
4557
@synthesize downloadComplete;
4558
@synthesize requestID;
4559
@synthesize runLoopMode;
4560
@synthesize statusTimer;
4561
@synthesize downloadCache;
4562
@synthesize cachePolicy;
4563
@synthesize cacheStoragePolicy;
4564
@synthesize didUseCachedResponse;
4565
@synthesize secondsToCache;
4566
@synthesize clientCertificates;
4567
@synthesize redirectURL;
4568
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
4569
@synthesize shouldContinueWhenAppEntersBackground;
4570
#endif
4571
@synthesize dataDecompressor;
4572
@synthesize shouldWaitToInflateCompressedResponses;
4573
@end