Statistics
| Branch: | Revision:

root / asi-http-request-with-pithos / Classes / ASIHTTPRequest.m @ cc176feb

History | View | Annotate | Download (180.3 kB)

1
//
2
//  ASIHTTPRequest.m
3
//
4
//  Created by Ben Copsey on 04/10/2007.
5
//  Copyright 2007-2011 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.1-61 2011-09-19";
28

    
29
static NSString *defaultUserAgent = nil;
30

    
31
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
32

    
33
static NSString *ASIHTTPRequestRunLoopMode = @"ASIHTTPRequestRunLoopMode";
34

    
35
static const CFOptionFlags kNetworkEvents =  kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred;
36

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

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

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

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

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

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

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

    
60
static NSError *ASIRequestCancelledError;
61
static NSError *ASIRequestTimedOutError;
62
static NSError *ASIAuthenticationError;
63
static NSError *ASIUnableToCreateRequestError;
64
static NSError *ASITooMuchRedirectionError;
65

    
66
static NSMutableArray *bandwidthUsageTracker = nil;
67
static unsigned long averageBandwidthUsedPerSecond = 0;
68

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

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

    
74
// An array of connectionInfo dictionaries.
75
// 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
76
static NSMutableArray *persistentConnectionsPool = nil;
77

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

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

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

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

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

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

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

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

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

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

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

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

    
124
static id <ASICacheDelegate> defaultCache = nil;
125

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

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

    
134
// The thread all requests will run on
135
// Hangs around forever, but will be blocked unless there are requests underway
136
static NSThread *networkThread = nil;
137

    
138
static NSOperationQueue *sharedQueue = nil;
139

    
140
// Private stuff
141
@interface ASIHTTPRequest ()
142

    
143
- (void)cancelLoad;
144

    
145
- (void)destroyReadStream;
146
- (void)scheduleReadStream;
147
- (void)unscheduleReadStream;
148

    
149
- (BOOL)willAskDelegateForCredentials;
150
- (BOOL)willAskDelegateForProxyCredentials;
151
- (void)askDelegateForProxyCredentials;
152
- (void)askDelegateForCredentials;
153
- (void)failAuthentication;
154

    
155
+ (void)measureBandwidthUsage;
156
+ (void)recordBandwidthUsage;
157

    
158
- (void)startRequest;
159
- (void)updateStatus:(NSTimer *)timer;
160
- (void)checkRequestStatus;
161
- (void)reportFailure;
162
- (void)reportFinished;
163
- (void)markAsFinished;
164
- (void)performRedirect;
165
- (BOOL)shouldTimeOut;
166
- (BOOL)willRedirect;
167
- (BOOL)willAskDelegateToConfirmRedirect;
168

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

    
174
// Handling Proxy autodetection and PAC file downloads
175
- (BOOL)configureProxies;
176
- (void)fetchPACFile;
177
- (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest;
178
- (void)runPACScript:(NSString *)script;
179
- (void)timeOutPACRead;
180

    
181
- (void)useDataFromCache;
182

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

    
186
#if TARGET_OS_IPHONE
187
+ (void)registerForNetworkReachabilityNotifications;
188
+ (void)unsubscribeFromNetworkReachabilityNotifications;
189
// Called when the status of the network changes
190
+ (void)reachabilityChanged:(NSNotification *)note;
191
#endif
192

    
193
#if NS_BLOCKS_AVAILABLE
194
- (void)performBlockOnMainThread:(ASIBasicBlock)block;
195
- (void)releaseBlocksOnMainThread;
196
+ (void)releaseBlocks:(NSArray *)blocks;
197
- (void)callBlock:(ASIBasicBlock)block;
198
#endif
199

    
200

    
201

    
202

    
203

    
204
@property (assign) BOOL complete;
205
@property (retain) NSArray *responseCookies;
206
@property (assign) int responseStatusCode;
207
@property (retain, nonatomic) NSDate *lastActivityTime;
208

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

    
243
@property (assign, nonatomic) BOOL isPACFileRequest;
244
@property (retain, nonatomic) ASIHTTPRequest *PACFileRequest;
245
@property (retain, nonatomic) NSInputStream *PACFileReadStream;
246
@property (retain, nonatomic) NSMutableData *PACFileData;
247

    
248
@property (assign, nonatomic, setter=setSynchronous:) BOOL isSynchronous;
249
@end
250

    
251

    
252
@implementation ASIHTTPRequest
253

    
254
#pragma mark init / dealloc
255

    
256
+ (void)initialize
257
{
258
	if (self == [ASIHTTPRequest class]) {
259
		persistentConnectionsPool = [[NSMutableArray alloc] init];
260
		connectionsLock = [[NSRecursiveLock alloc] init];
261
		progressLock = [[NSRecursiveLock alloc] init];
262
		bandwidthThrottlingLock = [[NSLock alloc] init];
263
		sessionCookiesLock = [[NSRecursiveLock alloc] init];
264
		sessionCredentialsLock = [[NSRecursiveLock alloc] init];
265
		delegateAuthenticationLock = [[NSRecursiveLock alloc] init];
266
		bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5];
267
		ASIRequestTimedOutError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]];  
268
		ASIAuthenticationError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]];
269
		ASIRequestCancelledError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]];
270
		ASIUnableToCreateRequestError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]];
271
		ASITooMuchRedirectionError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]];
272
		sharedQueue = [[NSOperationQueue alloc] init];
273
		[sharedQueue setMaxConcurrentOperationCount:4];
274

    
275
	}
276
}
277

    
278

    
279
- (id)initWithURL:(NSURL *)newURL
280
{
281
	self = [self init];
282
	[self setRequestMethod:@"GET"];
283

    
284
	[self setRunLoopMode:NSDefaultRunLoopMode];
285
	[self setShouldAttemptPersistentConnection:YES];
286
	[self setPersistentConnectionTimeoutSeconds:60.0];
287
	[self setShouldPresentCredentialsBeforeChallenge:YES];
288
	[self setShouldRedirect:YES];
289
	[self setShowAccurateProgress:YES];
290
	[self setShouldResetDownloadProgress:YES];
291
	[self setShouldResetUploadProgress:YES];
292
	[self setAllowCompressedResponse:YES];
293
	[self setShouldWaitToInflateCompressedResponses:YES];
294
	[self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
295
	[self setShouldPresentProxyAuthenticationDialog:YES];
296
	
297
	[self setTimeOutSeconds:[ASIHTTPRequest defaultTimeOutSeconds]];
298
	[self setUseSessionPersistence:YES];
299
	[self setUseCookiePersistence:YES];
300
	[self setValidatesSecureCertificate:YES];
301
	[self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]];
302
	[self setDidStartSelector:@selector(requestStarted:)];
303
	[self setDidReceiveResponseHeadersSelector:@selector(request:didReceiveResponseHeaders:)];
304
	[self setWillRedirectSelector:@selector(request:willRedirectToURL:)];
305
	[self setDidFinishSelector:@selector(requestFinished:)];
306
	[self setDidFailSelector:@selector(requestFailed:)];
307
	[self setDidReceiveDataSelector:@selector(request:didReceiveData:)];
308
	[self setURL:newURL];
309
	[self setCancelledLock:[[[NSRecursiveLock alloc] init] autorelease]];
310
	[self setDownloadCache:[[self class] defaultCache]];
311
	return self;
312
}
313

    
314
+ (id)requestWithURL:(NSURL *)newURL
315
{
316
	return [[[self alloc] initWithURL:newURL] autorelease];
317
}
318

    
319
+ (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache
320
{
321
	return [self requestWithURL:newURL usingCache:cache andCachePolicy:ASIUseDefaultCachePolicy];
322
}
323

    
324
+ (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache andCachePolicy:(ASICachePolicy)policy
325
{
326
	ASIHTTPRequest *request = [[[self alloc] initWithURL:newURL] autorelease];
327
	[request setDownloadCache:cache];
328
	[request setCachePolicy:policy];
329
	return request;
330
}
331

    
332
- (void)dealloc
333
{
334
	[self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
335
	if (requestAuthentication) {
336
		CFRelease(requestAuthentication);
337
	}
338
	if (proxyAuthentication) {
339
		CFRelease(proxyAuthentication);
340
	}
341
	if (request) {
342
		CFRelease(request);
343
	}
344
	if (clientCertificateIdentity) {
345
		CFRelease(clientCertificateIdentity);
346
	}
347
	[self cancelLoad];
348
	[redirectURL release];
349
	[statusTimer invalidate];
350
	[statusTimer release];
351
	[queue release];
352
	[userInfo release];
353
	[postBody release];
354
	[compressedPostBody release];
355
	[error release];
356
	[requestHeaders release];
357
	[requestCookies release];
358
	[downloadDestinationPath release];
359
	[temporaryFileDownloadPath release];
360
	[temporaryUncompressedDataDownloadPath release];
361
	[fileDownloadOutputStream release];
362
	[inflatedFileDownloadOutputStream release];
363
	[username release];
364
	[password release];
365
	[domain release];
366
	[authenticationRealm release];
367
	[authenticationScheme release];
368
	[requestCredentials release];
369
	[proxyHost release];
370
	[proxyType release];
371
	[proxyUsername release];
372
	[proxyPassword release];
373
	[proxyDomain release];
374
	[proxyAuthenticationRealm release];
375
	[proxyAuthenticationScheme release];
376
	[proxyCredentials release];
377
	[url release];
378
	[originalURL release];
379
	[lastActivityTime release];
380
	[responseCookies release];
381
	[rawResponseData release];
382
	[responseHeaders release];
383
	[requestMethod release];
384
	[cancelledLock release];
385
	[postBodyFilePath release];
386
	[compressedPostBodyFilePath release];
387
	[postBodyWriteStream release];
388
	[postBodyReadStream release];
389
	[PACurl release];
390
	[clientCertificates release];
391
	[responseStatusMessage release];
392
	[connectionInfo release];
393
	[requestID release];
394
	[dataDecompressor release];
395
	[userAgentString release];
396

    
397
	#if NS_BLOCKS_AVAILABLE
398
	[self releaseBlocksOnMainThread];
399
	#endif
400

    
401
	[super dealloc];
402
}
403

    
404
#if NS_BLOCKS_AVAILABLE
405
- (void)releaseBlocksOnMainThread
406
{
407
	NSMutableArray *blocks = [NSMutableArray array];
408
	if (completionBlock) {
409
		[blocks addObject:completionBlock];
410
		[completionBlock release];
411
		completionBlock = nil;
412
	}
413
	if (failureBlock) {
414
		[blocks addObject:failureBlock];
415
		[failureBlock release];
416
		failureBlock = nil;
417
	}
418
	if (startedBlock) {
419
		[blocks addObject:startedBlock];
420
		[startedBlock release];
421
		startedBlock = nil;
422
	}
423
	if (headersReceivedBlock) {
424
		[blocks addObject:headersReceivedBlock];
425
		[headersReceivedBlock release];
426
		headersReceivedBlock = nil;
427
	}
428
	if (bytesReceivedBlock) {
429
		[blocks addObject:bytesReceivedBlock];
430
		[bytesReceivedBlock release];
431
		bytesReceivedBlock = nil;
432
	}
433
	if (bytesSentBlock) {
434
		[blocks addObject:bytesSentBlock];
435
		[bytesSentBlock release];
436
		bytesSentBlock = nil;
437
	}
438
	if (downloadSizeIncrementedBlock) {
439
		[blocks addObject:downloadSizeIncrementedBlock];
440
		[downloadSizeIncrementedBlock release];
441
		downloadSizeIncrementedBlock = nil;
442
	}
443
	if (uploadSizeIncrementedBlock) {
444
		[blocks addObject:uploadSizeIncrementedBlock];
445
		[uploadSizeIncrementedBlock release];
446
		uploadSizeIncrementedBlock = nil;
447
	}
448
	if (dataReceivedBlock) {
449
		[blocks addObject:dataReceivedBlock];
450
		[dataReceivedBlock release];
451
		dataReceivedBlock = nil;
452
	}
453
	if (proxyAuthenticationNeededBlock) {
454
		[blocks addObject:proxyAuthenticationNeededBlock];
455
		[proxyAuthenticationNeededBlock release];
456
		proxyAuthenticationNeededBlock = nil;
457
	}
458
	if (authenticationNeededBlock) {
459
		[blocks addObject:authenticationNeededBlock];
460
		[authenticationNeededBlock release];
461
		authenticationNeededBlock = nil;
462
	}
463
	if (requestRedirectedBlock) {
464
		[blocks addObject:requestRedirectedBlock];
465
		[requestRedirectedBlock release];
466
		requestRedirectedBlock = nil;
467
	}
468
	[[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
469
}
470
// Always called on main thread
471
+ (void)releaseBlocks:(NSArray *)blocks
472
{
473
	// Blocks will be released when this method exits
474
}
475
#endif
476

    
477

    
478
#pragma mark setup request
479

    
480
- (void)addRequestHeader:(NSString *)header value:(NSString *)value
481
{
482
	if (!requestHeaders) {
483
		[self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]];
484
	}
485
	[requestHeaders setObject:value forKey:header];
486
}
487

    
488
// This function will be called either just before a request starts, or when postLength is needed, whichever comes first
489
// postLength must be set by the time this function is complete
490
- (void)buildPostBody
491
{
492

    
493
	if ([self haveBuiltPostBody]) {
494
		return;
495
	}
496
	
497
	// Are we submitting the request body from a file on disk
498
	if ([self postBodyFilePath]) {
499
		
500
		// If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream
501
		if ([self postBodyWriteStream]) {
502
			[[self postBodyWriteStream] close];
503
			[self setPostBodyWriteStream:nil];
504
		}
505

    
506
		
507
		NSString *path;
508
		if ([self shouldCompressRequestBody]) {
509
			if (![self compressedPostBodyFilePath]) {
510
				[self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
511
				
512
				NSError *err = nil;
513
				if (![ASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) {
514
					[self failWithError:err];
515
					return;
516
				}
517
			}
518
			path = [self compressedPostBodyFilePath];
519
		} else {
520
			path = [self postBodyFilePath];
521
		}
522
		NSError *err = nil;
523
		[self setPostLength:[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:path error:&err] fileSize]];
524
		if (err) {
525
			[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]]];
526
			return;
527
		}
528
		
529
	// Otherwise, we have an in-memory request body
530
	} else {
531
		if ([self shouldCompressRequestBody]) {
532
			NSError *err = nil;
533
			NSData *compressedBody = [ASIDataCompressor compressData:[self postBody] error:&err];
534
			if (err) {
535
				[self failWithError:err];
536
				return;
537
			}
538
			[self setCompressedPostBody:compressedBody];
539
			[self setPostLength:[[self compressedPostBody] length]];
540
		} else {
541
			[self setPostLength:[[self postBody] length]];
542
		}
543
	}
544
		
545
	if ([self postLength] > 0) {
546
		if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) {
547
			[self setRequestMethod:@"POST"];
548
		}
549
		[self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]];
550
	}
551
	[self setHaveBuiltPostBody:YES];
552

    
553
}
554

    
555
// Sets up storage for the post body
556
- (void)setupPostBody
557
{
558
	if ([self shouldStreamPostDataFromDisk]) {
559
		if (![self postBodyFilePath]) {
560
			[self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
561
			[self setDidCreateTemporaryPostDataFile:YES];
562
		}
563
		if (![self postBodyWriteStream]) {
564
			[self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]];
565
			[[self postBodyWriteStream] open];
566
		}
567
	} else {
568
		if (![self postBody]) {
569
			[self setPostBody:[[[NSMutableData alloc] init] autorelease]];
570
		}
571
	}	
572
}
573

    
574
- (void)appendPostData:(NSData *)data
575
{
576
	[self setupPostBody];
577
	if ([data length] == 0) {
578
		return;
579
	}
580
	if ([self shouldStreamPostDataFromDisk]) {
581
		[[self postBodyWriteStream] write:[data bytes] maxLength:[data length]];
582
	} else {
583
		[[self postBody] appendData:data];
584
	}
585
}
586

    
587
- (void)appendPostDataFromFile:(NSString *)file
588
{
589
	[self setupPostBody];
590
	NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease];
591
	[stream open];
592
	NSUInteger bytesRead;
593
	while ([stream hasBytesAvailable]) {
594
		
595
		unsigned char buffer[1024*256];
596
		bytesRead = [stream read:buffer maxLength:sizeof(buffer)];
597
		if (bytesRead == 0) {
598
			break;
599
		}
600
		if ([self shouldStreamPostDataFromDisk]) {
601
			[[self postBodyWriteStream] write:buffer maxLength:bytesRead];
602
		} else {
603
			[[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]];
604
		}
605
	}
606
	[stream close];
607
}
608

    
609
- (NSString *)requestMethod
610
{
611
	[[self cancelledLock] lock];
612
	NSString *m = requestMethod;
613
	[[self cancelledLock] unlock];
614
	return m;
615
}
616

    
617
- (void)setRequestMethod:(NSString *)newRequestMethod
618
{
619
	[[self cancelledLock] lock];
620
	if (requestMethod != newRequestMethod) {
621
		[requestMethod release];
622
		requestMethod = [newRequestMethod retain];
623
		if ([requestMethod isEqualToString:@"POST"] || [requestMethod isEqualToString:@"PUT"] || [postBody length] || postBodyFilePath) {
624
			[self setShouldAttemptPersistentConnection:NO];
625
		}
626
	}
627
	[[self cancelledLock] unlock];
628
}
629

    
630
- (NSURL *)url
631
{
632
	[[self cancelledLock] lock];
633
	NSURL *u = url;
634
	[[self cancelledLock] unlock];
635
	return u;
636
}
637

    
638

    
639
- (void)setURL:(NSURL *)newURL
640
{
641
	[[self cancelledLock] lock];
642
	if ([newURL isEqual:[self url]]) {
643
		[[self cancelledLock] unlock];
644
		return;
645
	}
646
	[url release];
647
	url = [newURL retain];
648
	if (requestAuthentication) {
649
		CFRelease(requestAuthentication);
650
		requestAuthentication = NULL;
651
	}
652
	if (proxyAuthentication) {
653
		CFRelease(proxyAuthentication);
654
		proxyAuthentication = NULL;
655
	}
656
	if (request) {
657
		CFRelease(request);
658
		request = NULL;
659
	}
660
	[self setRedirectURL:nil];
661
	[[self cancelledLock] unlock];
662
}
663

    
664
- (id)delegate
665
{
666
	[[self cancelledLock] lock];
667
	id d = delegate;
668
	[[self cancelledLock] unlock];
669
	return d;
670
}
671

    
672
- (void)setDelegate:(id)newDelegate
673
{
674
	[[self cancelledLock] lock];
675
	delegate = newDelegate;
676
	[[self cancelledLock] unlock];
677
}
678

    
679
- (id)queue
680
{
681
	[[self cancelledLock] lock];
682
	id q = queue;
683
	[[self cancelledLock] unlock];
684
	return q;
685
}
686

    
687

    
688
- (void)setQueue:(id)newQueue
689
{
690
	[[self cancelledLock] lock];
691
	if (newQueue != queue) {
692
		[queue release];
693
		queue = [newQueue retain];
694
	}
695
	[[self cancelledLock] unlock];
696
}
697

    
698
#pragma mark get information about this request
699

    
700
// cancel the request - this must be run on the same thread as the request is running on
701
- (void)cancelOnRequestThread
702
{
703
	#if DEBUG_REQUEST_STATUS
704
	ASI_DEBUG_LOG(@"[STATUS] Request cancelled: %@",self);
705
	#endif
706
    
707
	[[self cancelledLock] lock];
708

    
709
    if ([self isCancelled] || [self complete]) {
710
		[[self cancelledLock] unlock];
711
		return;
712
	}
713
	[self failWithError:ASIRequestCancelledError];
714
	[self setComplete:YES];
715
	[self cancelLoad];
716
	
717
	CFRetain(self);
718
    [self willChangeValueForKey:@"isCancelled"];
719
    cancelled = YES;
720
    [self didChangeValueForKey:@"isCancelled"];
721
    
722
	[[self cancelledLock] unlock];
723
	CFRelease(self);
724
}
725

    
726
- (void)cancel
727
{
728
    [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];    
729
}
730

    
731
- (void)clearDelegatesAndCancel
732
{
733
	[[self cancelledLock] lock];
734

    
735
	// Clear delegates
736
	[self setDelegate:nil];
737
	[self setQueue:nil];
738
	[self setDownloadProgressDelegate:nil];
739
	[self setUploadProgressDelegate:nil];
740

    
741
	#if NS_BLOCKS_AVAILABLE
742
	// Clear blocks
743
	[self releaseBlocksOnMainThread];
744
	#endif
745

    
746
	[[self cancelledLock] unlock];
747
	[self cancel];
748
}
749

    
750

    
751
- (BOOL)isCancelled
752
{
753
    BOOL result;
754
    
755
	[[self cancelledLock] lock];
756
    result = cancelled;
757
    [[self cancelledLock] unlock];
758
    
759
    return result;
760
}
761

    
762
// Call this method to get the received data as an NSString. Don't use for binary data!
763
- (NSString *)responseString
764
{
765
	NSData *data = [self responseData];
766
	if (!data) {
767
		return nil;
768
	}
769
	
770
	return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
771
}
772

    
773
- (BOOL)isResponseCompressed
774
{
775
	NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
776
	return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
777
}
778

    
779
- (NSData *)responseData
780
{	
781
	if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) {
782
		return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL];
783
	} else {
784
		return [self rawResponseData];
785
	}
786
	return nil;
787
}
788

    
789
#pragma mark running a request
790

    
791
- (void)startSynchronous
792
{
793
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
794
	ASI_DEBUG_LOG(@"[STATUS] Starting synchronous request %@",self);
795
#endif
796
	[self setSynchronous:YES];
797
	[self setRunLoopMode:ASIHTTPRequestRunLoopMode];
798
	[self setInProgress:YES];
799

    
800
	if (![self isCancelled] && ![self complete]) {
801
		[self main];
802
		while (!complete) {
803
			[[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]];
804
		}
805
	}
806

    
807
	[self setInProgress:NO];
808
}
809

    
810
- (void)start
811
{
812
	[self setInProgress:YES];
813
	[self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
814
}
815

    
816
- (void)startAsynchronous
817
{
818
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
819
	ASI_DEBUG_LOG(@"[STATUS] Starting asynchronous request %@",self);
820
#endif
821
	[sharedQueue addOperation:self];
822
}
823

    
824
#pragma mark concurrency
825

    
826
- (BOOL)isConcurrent
827
{
828
    return YES;
829
}
830

    
831
- (BOOL)isFinished 
832
{
833
	return finished;
834
}
835

    
836
- (BOOL)isExecuting {
837
	return [self inProgress];
838
}
839

    
840
#pragma mark request logic
841

    
842
// Create the request
843
- (void)main
844
{
845
	@try {
846
		
847
		[[self cancelledLock] lock];
848
		
849
		#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
850
		if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
851
            if (!backgroundTask || backgroundTask == UIBackgroundTaskInvalid) {
852
                backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
853
                    // Synchronize the cleanup call on the main thread in case
854
                    // the task actually finishes at around the same time.
855
                    dispatch_async(dispatch_get_main_queue(), ^{
856
                        if (backgroundTask != UIBackgroundTaskInvalid)
857
                        {
858
                            [[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
859
                            backgroundTask = UIBackgroundTaskInvalid;
860
                            [self cancel];
861
                        }
862
                    });
863
                }];
864
            }
865
		}
866
		#endif
867

    
868

    
869
		// A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed.
870
		if ([self error]) {
871
			[self setComplete:YES];
872
			[self markAsFinished];
873
			return;		
874
		}
875

    
876
		[self setComplete:NO];
877
		[self setDidUseCachedResponse:NO];
878
		
879
		if (![self url]) {
880
			[self failWithError:ASIUnableToCreateRequestError];
881
			return;		
882
		}
883
		
884
		// Must call before we create the request so that the request method can be set if needs be
885
		if (![self mainRequest]) {
886
			[self buildPostBody];
887
		}
888
		
889
		if (![[self requestMethod] isEqualToString:@"GET"]) {
890
			[self setDownloadCache:nil];
891
		}
892
		
893
		
894
		// If we're redirecting, we'll already have a CFHTTPMessageRef
895
		if (request) {
896
			CFRelease(request);
897
		}
898

    
899
		// Create a new HTTP request.
900
		request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
901
		if (!request) {
902
			[self failWithError:ASIUnableToCreateRequestError];
903
			return;
904
		}
905

    
906
		//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
907
		if ([self mainRequest]) {
908
			[[self mainRequest] buildRequestHeaders];
909
		}
910
		
911
		// 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)
912
		[self buildRequestHeaders];
913
		
914
		if ([self downloadCache]) {
915

    
916
			// If this request should use the default policy, set its policy to the download cache's default policy
917
			if (![self cachePolicy]) {
918
				[self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
919
			}
920

    
921
			// If have have cached data that is valid for this request, use that and stop
922
			if ([[self downloadCache] canUseCachedDataForRequest:self]) {
923
				[self useDataFromCache];
924
				return;
925
			}
926

    
927
			// 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
928
			if ([self cachePolicy] & (ASIAskServerIfModifiedWhenStaleCachePolicy|ASIAskServerIfModifiedCachePolicy)) {
929

    
930
				NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
931
				if (cachedHeaders) {
932
					NSString *etag = [cachedHeaders objectForKey:@"Etag"];
933
					if (etag) {
934
						[[self requestHeaders] setObject:etag forKey:@"If-None-Match"];
935
					}
936
					NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"];
937
					if (lastModified) {
938
						[[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"];
939
					}
940
				}
941
			}
942
		}
943

    
944
		[self applyAuthorizationHeader];
945
		
946
		
947
		NSString *header;
948
		for (header in [self requestHeaders]) {
949
			CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]);
950
		}
951

    
952
		// If we immediately have access to proxy settings, start the request
953
		// Otherwise, we'll start downloading the proxy PAC file, and call startRequest once that process is complete
954
		if ([self configureProxies]) {
955
			[self startRequest];
956
		}
957

    
958
	} @catch (NSException *exception) {
959
		NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]];
960
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]];
961

    
962
	} @finally {
963
		[[self cancelledLock] unlock];
964
	}
965
}
966

    
967
- (void)applyAuthorizationHeader
968
{
969
	// Do we want to send credentials before we are asked for them?
970
	if (![self shouldPresentCredentialsBeforeChallenge]) {
971
		#if DEBUG_HTTP_AUTHENTICATION
972
		ASI_DEBUG_LOG(@"[AUTH] Request %@ will not send credentials to the server until it asks for them",self);
973
		#endif
974
		return;
975
	}
976

    
977
	NSDictionary *credentials = nil;
978

    
979
	// Do we already have an auth header?
980
	if (![[self requestHeaders] objectForKey:@"Authorization"]) {
981

    
982
		// If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header
983
		if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
984
			[self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
985

    
986
			#if DEBUG_HTTP_AUTHENTICATION
987
			ASI_DEBUG_LOG(@"[AUTH] Request %@ has a username and password set, and was manually configured to use BASIC. Will send credentials without waiting for an authentication challenge",self);	
988
			#endif
989

    
990
		} else {
991

    
992
			// See if we have any cached credentials we can use in the session store
993
			if ([self useSessionPersistence]) {
994
				credentials = [self findSessionAuthenticationCredentials];
995

    
996
				if (credentials) {
997

    
998
					// When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
999
					// (credentials for Digest and NTLM will always be stored like this)
1000
					if ([credentials objectForKey:@"Authentication"]) {
1001

    
1002
						// If we've already talked to this server and have valid credentials, let's apply them to the request
1003
						if (CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
1004
							[self setAuthenticationScheme:[credentials objectForKey:@"AuthenticationScheme"]];
1005
							#if DEBUG_HTTP_AUTHENTICATION
1006
							ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached credentials (%@), will reuse without waiting for an authentication challenge",self,[credentials objectForKey:@"AuthenticationScheme"]);
1007
							#endif
1008
						} else {
1009
							[[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
1010
							#if DEBUG_HTTP_AUTHENTICATION
1011
							ASI_DEBUG_LOG(@"[AUTH] Failed to apply cached credentials to request %@. These will be removed from the session store, and this request will wait for an authentication challenge",self);
1012
							#endif
1013
						}
1014

    
1015
					// If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
1016
					// When this happens, we'll need to create the Authorization header ourselves
1017
					} else {
1018
						NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
1019
						[self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
1020
						#if DEBUG_HTTP_AUTHENTICATION
1021
						ASI_DEBUG_LOG(@"[AUTH] Request %@ found cached BASIC credentials from a previous request. Will send credentials without waiting for an authentication challenge",self);
1022
						#endif
1023
					}
1024
				}
1025
			}
1026
		}
1027
	}
1028

    
1029
	// Apply proxy authentication credentials
1030
	if ([self useSessionPersistence]) {
1031
		credentials = [self findSessionProxyAuthenticationCredentials];
1032
		if (credentials) {
1033
			if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
1034
				[[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
1035
			}
1036
		}
1037
	}
1038
}
1039

    
1040
- (void)applyCookieHeader
1041
{
1042
	// Add cookies from the persistent (mac os global) store
1043
	if ([self useCookiePersistence]) {
1044
		NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[self url] absoluteURL]];
1045
		if (cookies) {
1046
			[[self requestCookies] addObjectsFromArray:cookies];
1047
		}
1048
	}
1049
	
1050
	// Apply request cookies
1051
	NSArray *cookies;
1052
	if ([self mainRequest]) {
1053
		cookies = [[self mainRequest] requestCookies];
1054
	} else {
1055
		cookies = [self requestCookies];
1056
	}
1057
	if ([cookies count] > 0) {
1058
		NSHTTPCookie *cookie;
1059
		NSString *cookieHeader = nil;
1060
		for (cookie in cookies) {
1061
			if (!cookieHeader) {
1062
				cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]];
1063
			} else {
1064
				cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]];
1065
			}
1066
		}
1067
		if (cookieHeader) {
1068
			[self addRequestHeader:@"Cookie" value:cookieHeader];
1069
		}
1070
	}	
1071
}
1072

    
1073
- (void)buildRequestHeaders
1074
{
1075
	if ([self haveBuiltRequestHeaders]) {
1076
		return;
1077
	}
1078
	[self setHaveBuiltRequestHeaders:YES];
1079
	
1080
	if ([self mainRequest]) {
1081
		for (NSString *header in [[self mainRequest] requestHeaders]) {
1082
			[self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]];
1083
		}
1084
		return;
1085
	}
1086
	
1087
	[self applyCookieHeader];
1088
	
1089
	// Build and set the user agent string if the request does not already have a custom user agent specified
1090
	if (![[self requestHeaders] objectForKey:@"User-Agent"]) {
1091
		NSString *tempUserAgentString = [self userAgentString];
1092
		if (!tempUserAgentString) {
1093
			tempUserAgentString = [ASIHTTPRequest defaultUserAgentString];
1094
		}
1095
		if (tempUserAgentString) {
1096
			[self addRequestHeader:@"User-Agent" value:tempUserAgentString];
1097
		}
1098
	}
1099
	
1100
	
1101
	// Accept a compressed response
1102
	if ([self allowCompressedResponse]) {
1103
		[self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
1104
	}
1105
	
1106
	// Configure a compressed request body
1107
	if ([self shouldCompressRequestBody]) {
1108
		[self addRequestHeader:@"Content-Encoding" value:@"gzip"];
1109
	}
1110
	
1111
	// Should this request resume an existing download?
1112
	[self updatePartialDownloadSize];
1113
	if ([self partialDownloadSize]) {
1114
		[self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]];
1115
	}
1116
}
1117

    
1118
- (void)updatePartialDownloadSize
1119
{
1120
	NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
1121

    
1122
	if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) {
1123
		NSError *err = nil;
1124
		[self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
1125
		if (err) {
1126
			[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]]];
1127
			return;
1128
		}
1129
	}
1130
}
1131

    
1132
- (void)startRequest
1133
{
1134
	if ([self isCancelled]) {
1135
		return;
1136
	}
1137
	
1138
	[self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]];
1139
	
1140
	[self setDownloadComplete:NO];
1141
	[self setComplete:NO];
1142
	[self setTotalBytesRead:0];
1143
	[self setLastBytesRead:0];
1144
	
1145
	if ([self redirectCount] == 0) {
1146
		[self setOriginalURL:[self url]];
1147
	}
1148
	
1149
	// If we're retrying a request, let's remove any progress we made
1150
	if ([self lastBytesSent] > 0) {
1151
		[self removeUploadProgressSoFar];
1152
	}
1153
	
1154
	[self setLastBytesSent:0];
1155
	[self setContentLength:0];
1156
	[self setResponseHeaders:nil];
1157
	if (![self downloadDestinationPath]) {
1158
		[self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
1159
    }
1160
	
1161
	
1162
    //
1163
	// Create the stream for the request
1164
	//
1165

    
1166
	NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
1167

    
1168
	[self setReadStreamIsScheduled:NO];
1169
	
1170
	// Do we need to stream the request body from disk
1171
	if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [fileManager fileExistsAtPath:[self postBodyFilePath]]) {
1172
		
1173
		// Are we gzipping the request body?
1174
		if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) {
1175
			[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
1176
		} else {
1177
			[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
1178
		}
1179
		[self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];    
1180
    } else {
1181
		
1182
		// 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
1183
		if ([self postBody] && [[self postBody] length] > 0) {
1184
			if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
1185
				[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
1186
			} else if ([self postBody]) {
1187
				[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
1188
			}
1189
			[self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
1190
		
1191
		} else {
1192
			[self setReadStream:[NSMakeCollectable(CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request)) autorelease]];
1193
		}
1194
	}
1195

    
1196
	if (![self readStream]) {
1197
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
1198
        return;
1199
    }
1200

    
1201

    
1202
    
1203
    
1204
    //
1205
    // Handle SSL certificate settings
1206
    //
1207

    
1208
    if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {       
1209
       
1210
        // Tell CFNetwork not to validate SSL certificates
1211
        if (![self validatesSecureCertificate]) {
1212
            // see: http://iphonedevelopment.blogspot.com/2010/05/nsstream-tcp-and-ssl.html
1213
            
1214
            NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
1215
                                      [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
1216
                                      [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
1217
                                      [NSNumber numberWithBool:NO],  kCFStreamSSLValidatesCertificateChain,
1218
                                      kCFNull,kCFStreamSSLPeerName,
1219
                                      nil];
1220
            
1221
            CFReadStreamSetProperty((CFReadStreamRef)[self readStream], 
1222
                                    kCFStreamPropertySSLSettings, 
1223
                                    (CFTypeRef)sslProperties);
1224
            [sslProperties release];
1225
        } 
1226
        
1227
        // Tell CFNetwork to use a client certificate
1228
        if (clientCertificateIdentity) {
1229
            NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
1230
            
1231
			NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
1232

    
1233
			// The first object in the array is our SecIdentityRef
1234
			[certificates addObject:(id)clientCertificateIdentity];
1235

    
1236
			// If we've added any additional certificates, add them too
1237
			for (id cert in clientCertificates) {
1238
				[certificates addObject:cert];
1239
			}
1240
            
1241
            [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
1242
            
1243
            CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
1244
        }
1245
        
1246
    }
1247

    
1248
	//
1249
	// Handle proxy settings
1250
	//
1251

    
1252
 	if ([self proxyHost] && [self proxyPort]) {
1253
		NSString *hostKey;
1254
		NSString *portKey;
1255

    
1256
		if (![self proxyType]) {
1257
			[self setProxyType:(NSString *)kCFProxyTypeHTTP];
1258
		}
1259

    
1260
		if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
1261
			hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
1262
			portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
1263
		} else {
1264
			hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
1265
			portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
1266
			if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
1267
				hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
1268
				portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
1269
			}
1270
		}
1271
		NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil];
1272

    
1273
		if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
1274
			CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse);
1275
		} else {
1276
			CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse);
1277
		}
1278
	}
1279

    
1280

    
1281
	//
1282
	// Handle persistent connections
1283
	//
1284
	
1285
	[ASIHTTPRequest expirePersistentConnections];
1286

    
1287
	[connectionsLock lock];
1288
	
1289
	
1290
	if (![[self url] host] || ![[self url] scheme]) {
1291
		[self setConnectionInfo:nil];
1292
		[self setShouldAttemptPersistentConnection:NO];
1293
	}
1294
	
1295
	// 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
1296
	NSInputStream *oldStream = nil;
1297
	
1298
	// Use a persistent connection if possible
1299
	if ([self shouldAttemptPersistentConnection]) {
1300
		
1301

    
1302
		// If we are redirecting, we will re-use the current connection only if we are connecting to the same server
1303
		if ([self connectionInfo]) {
1304
			
1305
			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]) {
1306
				[self setConnectionInfo:nil];
1307

    
1308
			// Check if we should have expired this connection
1309
			} else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) {
1310
				#if DEBUG_PERSISTENT_CONNECTIONS
1311
				ASI_DEBUG_LOG(@"[CONNECTION] Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]);
1312
				#endif
1313
				[persistentConnectionsPool removeObject:[self connectionInfo]];
1314
				[self setConnectionInfo:nil];
1315

    
1316
			} else if ([[self connectionInfo] objectForKey:@"request"] != nil) {
1317
                //Some other request reused this connection already - we'll have to create a new one
1318
				#if DEBUG_PERSISTENT_CONNECTIONS
1319
                ASI_DEBUG_LOG(@"%@ - Not re-using connection #%i for request #%i because it is already used by request #%i",self,[[[self connectionInfo] objectForKey:@"id"] intValue],[[self requestID] intValue],[[[self connectionInfo] objectForKey:@"request"] intValue]);
1320
				#endif
1321
                [self setConnectionInfo:nil];
1322
            }
1323
		}
1324
		
1325
		
1326
		
1327
		if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
1328
			
1329
			// Look for a connection to the same server in the pool
1330
			for (NSMutableDictionary *existingConnection in persistentConnectionsPool) {
1331
				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]) {
1332
					[self setConnectionInfo:existingConnection];
1333
				}
1334
			}
1335
		}
1336
		
1337
		if ([[self connectionInfo] objectForKey:@"stream"]) {
1338
			oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain];
1339

    
1340
		}
1341
		
1342
		// 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
1343
		if (![self connectionInfo]) {
1344
			[self setConnectionInfo:[NSMutableDictionary dictionary]];
1345
			nextConnectionNumberToCreate++;
1346
			[[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
1347
			[[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
1348
			[[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
1349
			[[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
1350
			[persistentConnectionsPool addObject:[self connectionInfo]];
1351
		}
1352
		
1353
		// If we are retrying this request, it will already have a requestID
1354
		if (![self requestID]) {
1355
			nextRequestID++;
1356
			[self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]];
1357
		}
1358
		[[self connectionInfo] setObject:[self requestID] forKey:@"request"];		
1359
		[[self connectionInfo] setObject:[self readStream] forKey:@"stream"];
1360
		CFReadStreamSetProperty((CFReadStreamRef)[self readStream],  kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
1361
		
1362
		#if DEBUG_PERSISTENT_CONNECTIONS
1363
		ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]);
1364
		#endif
1365
		
1366
		
1367
		// Tag the stream with an id that tells it which connection to use behind the scenes
1368
		// See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
1369
		
1370
		CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
1371
	
1372
	} else {
1373
		#if DEBUG_PERSISTENT_CONNECTIONS
1374
		ASI_DEBUG_LOG(@"[CONNECTION] Request %@ will not use a persistent connection",self);
1375
		#endif
1376
	}
1377
	
1378
	[connectionsLock unlock];
1379

    
1380
	// Schedule the stream
1381
	if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) {
1382
		[self scheduleReadStream];
1383
	}
1384
	
1385
	BOOL streamSuccessfullyOpened = NO;
1386

    
1387

    
1388
   // Start the HTTP connection
1389
	CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
1390
    if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
1391
		if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
1392
			streamSuccessfullyOpened = YES;
1393
		}
1394
	}
1395
	
1396
	// Here, we'll close the stream that was previously using this connection, if there was one
1397
	// 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
1398
	// http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html
1399
	if (oldStream) {
1400
		[oldStream close];
1401
		[oldStream release];
1402
		oldStream = nil;
1403
	}
1404

    
1405
	if (!streamSuccessfullyOpened) {
1406
		[self setConnectionCanBeReused:NO];
1407
		[self destroyReadStream];
1408
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
1409
		return;	
1410
	}
1411
	
1412
	if (![self mainRequest]) {
1413
		if ([self shouldResetUploadProgress]) {
1414
			if ([self showAccurateProgress]) {
1415
				[self incrementUploadSizeBy:[self postLength]];
1416
			} else {
1417
				[self incrementUploadSizeBy:1];	 
1418
			}
1419
			[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1];
1420
		}
1421
		if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) {
1422
			[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1];
1423
		}
1424
	}	
1425
	
1426
	
1427
	// Record when the request started, so we can timeout if nothing happens
1428
	[self setLastActivityTime:[NSDate date]];
1429
	[self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]];
1430
	[[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]];
1431
}
1432

    
1433
- (void)setStatusTimer:(NSTimer *)timer
1434
{
1435
	CFRetain(self);
1436
	// We must invalidate the old timer here, not before we've created and scheduled a new timer
1437
	// This is because the timer may be the only thing retaining an asynchronous request
1438
	if (statusTimer && timer != statusTimer) {
1439
		[statusTimer invalidate];
1440
		[statusTimer release];
1441
	}
1442
	statusTimer = [timer retain];
1443
	CFRelease(self);
1444
}
1445

    
1446
// This gets fired every 1/4 of a second to update the progress and work out if we need to timeout
1447
- (void)updateStatus:(NSTimer*)timer
1448
{
1449
	[self checkRequestStatus];
1450
	if (![self inProgress]) {
1451
		[self setStatusTimer:nil];
1452
	}
1453
}
1454

    
1455
- (void)performRedirect
1456
{
1457
	[self setURL:[self redirectURL]];
1458
	[self setComplete:YES];
1459
	[self setNeedsRedirect:NO];
1460
	[self setRedirectCount:[self redirectCount]+1];
1461

    
1462
	if ([self redirectCount] > RedirectionLimit) {
1463
		// Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool.
1464
		[self failWithError:ASITooMuchRedirectionError];
1465
		[self setComplete:YES];
1466
	} else {
1467
		// Go all the way back to the beginning and build the request again, so that we can apply any new cookies
1468
		[self main];
1469
	}
1470
}
1471

    
1472
// Called by delegate to resume loading with a new url after the delegate received request:willRedirectToURL:
1473
- (void)redirectToURL:(NSURL *)newURL
1474
{
1475
	[self setRedirectURL:newURL];
1476
	[self performSelector:@selector(performRedirect) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
1477
}
1478

    
1479
- (BOOL)shouldTimeOut
1480
{
1481
	NSTimeInterval secondsSinceLastActivity = [[NSDate date] timeIntervalSinceDate:lastActivityTime];
1482
	// See if we need to timeout
1483
	if ([self readStream] && [self readStreamIsScheduled] && [self lastActivityTime] && [self timeOutSeconds] > 0 && secondsSinceLastActivity > [self timeOutSeconds]) {
1484
		
1485
		// We have no body, or we've sent more than the upload buffer size,so we can safely time out here
1486
		if ([self postLength] == 0 || ([self uploadBufferSize] > 0 && [self totalBytesSent] > [self uploadBufferSize])) {
1487
			return YES;
1488
			
1489
		// ***Black magic warning***
1490
		// We have a body, but we've taken longer than timeOutSeconds to upload the first small chunk of data
1491
		// 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.
1492
		} else if (secondsSinceLastActivity > [self timeOutSeconds]*1.5) {
1493
			return YES;
1494
		}
1495
	}
1496
	return NO;
1497
}
1498

    
1499
- (void)checkRequestStatus
1500
{
1501
	// We won't let the request cancel while we're updating progress / checking for a timeout
1502
	[[self cancelledLock] lock];
1503
	// See if our NSOperationQueue told us to cancel
1504
	if ([self isCancelled] || [self complete]) {
1505
		[[self cancelledLock] unlock];
1506
		return;
1507
	}
1508
	
1509
	[self performThrottling];
1510
	
1511
	if ([self shouldTimeOut]) {			
1512
		// Do we need to auto-retry this request?
1513
		if ([self numberOfTimesToRetryOnTimeout] > [self retryCount]) {
1514

    
1515
			// If we are resuming a download, we may need to update the Range header to take account of data we've just downloaded
1516
			[self updatePartialDownloadSize];
1517
			if ([self partialDownloadSize]) {
1518
				CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)@"Range", (CFStringRef)[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]);
1519
			}
1520
			[self setRetryCount:[self retryCount]+1];
1521
			[self unscheduleReadStream];
1522
			[[self cancelledLock] unlock];
1523
			[self startRequest];
1524
			return;
1525
		}
1526
		[self failWithError:ASIRequestTimedOutError];
1527
		[self cancelLoad];
1528
		[self setComplete:YES];
1529
		[[self cancelledLock] unlock];
1530
		return;
1531
	}
1532

    
1533
	// readStream will be null if we aren't currently running (perhaps we're waiting for a delegate to supply credentials)
1534
	if ([self readStream]) {
1535
		
1536
		// If we have a post body
1537
		if ([self postLength]) {
1538
		
1539
			[self setLastBytesSent:totalBytesSent];	
1540
			
1541
			// Find out how much data we've uploaded so far
1542
			[self setTotalBytesSent:[[NSMakeCollectable(CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount)) autorelease] unsignedLongLongValue]];
1543
			if (totalBytesSent > lastBytesSent) {
1544
				
1545
				// We've uploaded more data,  reset the timeout
1546
				[self setLastActivityTime:[NSDate date]];
1547
				[ASIHTTPRequest incrementBandwidthUsedInLastSecond:(unsigned long)(totalBytesSent-lastBytesSent)];		
1548
						
1549
				#if DEBUG_REQUEST_STATUS
1550
				if ([self totalBytesSent] == [self postLength]) {
1551
					ASI_DEBUG_LOG(@"[STATUS] Request %@ finished uploading data",self);
1552
				}
1553
				#endif
1554
			}
1555
		}
1556
			
1557
		[self updateProgressIndicators];
1558

    
1559
	}
1560
	
1561
	[[self cancelledLock] unlock];
1562
}
1563

    
1564

    
1565
// Cancel loading and clean up. DO NOT USE THIS TO CANCEL REQUESTS - use [request cancel] instead
1566
- (void)cancelLoad
1567
{
1568
	// If we're in the middle of downloading a PAC file, let's stop that first
1569
	if (PACFileReadStream) {
1570
		[PACFileReadStream setDelegate:nil];
1571
		[PACFileReadStream close];
1572
		[self setPACFileReadStream:nil];
1573
		[self setPACFileData:nil];
1574
	} else if (PACFileRequest) {
1575
		[PACFileRequest setDelegate:nil];
1576
		[PACFileRequest cancel];
1577
		[self setPACFileRequest:nil];
1578
	}
1579

    
1580
    [self destroyReadStream];
1581
	
1582
	[[self postBodyReadStream] close];
1583
	[self setPostBodyReadStream:nil];
1584
	
1585
    if ([self rawResponseData]) {
1586
		if (![self complete]) {
1587
			[self setRawResponseData:nil];
1588
		}
1589
	// If we were downloading to a file
1590
	} else if ([self temporaryFileDownloadPath]) {
1591
		[[self fileDownloadOutputStream] close];
1592
		[self setFileDownloadOutputStream:nil];
1593
		
1594
		[[self inflatedFileDownloadOutputStream] close];
1595
		[self setInflatedFileDownloadOutputStream:nil];
1596
		
1597
		// If we haven't said we might want to resume, let's remove the temporary file too
1598
		if (![self complete]) {
1599
			if (![self allowResumeForFileDownloads]) {
1600
				[self removeTemporaryDownloadFile];
1601
			}
1602
			[self removeTemporaryUncompressedDownloadFile];
1603
		}
1604
	}
1605
	
1606
	// Clean up any temporary file used to store request body for streaming
1607
	if (![self authenticationNeeded] && ![self willRetryRequest] && [self didCreateTemporaryPostDataFile]) {
1608
		[self removeTemporaryUploadFile];
1609
		[self removeTemporaryCompressedUploadFile];
1610
		[self setDidCreateTemporaryPostDataFile:NO];
1611
	}
1612
}
1613

    
1614
#pragma mark HEAD request
1615

    
1616
// Used by ASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself)
1617
- (ASIHTTPRequest *)HEADRequest
1618
{
1619
	ASIHTTPRequest *headRequest = [[self class] requestWithURL:[self url]];
1620
	
1621
	// Copy the properties that make sense for a HEAD request
1622
	[headRequest setRequestHeaders:[[[self requestHeaders] mutableCopy] autorelease]];
1623
	[headRequest setRequestCookies:[[[self requestCookies] mutableCopy] autorelease]];
1624
	[headRequest setUseCookiePersistence:[self useCookiePersistence]];
1625
	[headRequest setUseKeychainPersistence:[self useKeychainPersistence]];
1626
	[headRequest setUseSessionPersistence:[self useSessionPersistence]];
1627
	[headRequest setAllowCompressedResponse:[self allowCompressedResponse]];
1628
	[headRequest setUsername:[self username]];
1629
	[headRequest setPassword:[self password]];
1630
	[headRequest setDomain:[self domain]];
1631
	[headRequest setProxyUsername:[self proxyUsername]];
1632
	[headRequest setProxyPassword:[self proxyPassword]];
1633
	[headRequest setProxyDomain:[self proxyDomain]];
1634
	[headRequest setProxyHost:[self proxyHost]];
1635
	[headRequest setProxyPort:[self proxyPort]];
1636
	[headRequest setProxyType:[self proxyType]];
1637
	[headRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]];
1638
	[headRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]];
1639
	[headRequest setTimeOutSeconds:[self timeOutSeconds]];
1640
	[headRequest setUseHTTPVersionOne:[self useHTTPVersionOne]];
1641
	[headRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
1642
    [headRequest setClientCertificateIdentity:clientCertificateIdentity];
1643
	[headRequest setClientCertificates:[[clientCertificates copy] autorelease]];
1644
	[headRequest setPACurl:[self PACurl]];
1645
	[headRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
1646
	[headRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]];
1647
	[headRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]];
1648
	[headRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]];
1649
	[headRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]];
1650
	
1651
	[headRequest setMainRequest:self];
1652
	[headRequest setRequestMethod:@"HEAD"];
1653
	return headRequest;
1654
}
1655

    
1656

    
1657
#pragma mark upload/download progress
1658

    
1659

    
1660
- (void)updateProgressIndicators
1661
{
1662
	//Only update progress if this isn't a HEAD request used to preset the content-length
1663
	if (![self mainRequest]) {
1664
		if ([self showAccurateProgress] || ([self complete] && ![self updatedProgress])) {
1665
			[self updateUploadProgress];
1666
			[self updateDownloadProgress];
1667
		}
1668
	}
1669
}
1670

    
1671
- (id)uploadProgressDelegate
1672
{
1673
	[[self cancelledLock] lock];
1674
	id d = [[uploadProgressDelegate retain] autorelease];
1675
	[[self cancelledLock] unlock];
1676
	return d;
1677
}
1678

    
1679
- (void)setUploadProgressDelegate:(id)newDelegate
1680
{
1681
	[[self cancelledLock] lock];
1682
	uploadProgressDelegate = newDelegate;
1683

    
1684
	#if !TARGET_OS_IPHONE
1685
	// If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView
1686
	double max = 1.0;
1687
	[ASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&uploadProgressDelegate withObject:nil amount:&max callerToRetain:nil];
1688
	#endif
1689
	[[self cancelledLock] unlock];
1690
}
1691

    
1692
- (id)downloadProgressDelegate
1693
{
1694
	[[self cancelledLock] lock];
1695
	id d = [[downloadProgressDelegate retain] autorelease];
1696
	[[self cancelledLock] unlock];
1697
	return d;
1698
}
1699

    
1700
- (void)setDownloadProgressDelegate:(id)newDelegate
1701
{
1702
	[[self cancelledLock] lock];
1703
	downloadProgressDelegate = newDelegate;
1704

    
1705
	#if !TARGET_OS_IPHONE
1706
	// If the downloadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView
1707
	double max = 1.0;
1708
	[ASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&downloadProgressDelegate withObject:nil amount:&max callerToRetain:nil];	
1709
	#endif
1710
	[[self cancelledLock] unlock];
1711
}
1712

    
1713

    
1714
- (void)updateDownloadProgress
1715
{
1716
	// We won't update download progress until we've examined the headers, since we might need to authenticate
1717
	if (![self responseHeaders] || [self needsRedirect] || !([self contentLength] || [self complete])) {
1718
		return;
1719
	}
1720
		
1721
	unsigned long long bytesReadSoFar = [self totalBytesRead]+[self partialDownloadSize];
1722
	unsigned long long value = 0;
1723
	
1724
	if ([self showAccurateProgress] && [self contentLength]) {
1725
		value = bytesReadSoFar-[self lastBytesRead];
1726
		if (value == 0) {
1727
			return;
1728
		}
1729
	} else {
1730
		value = 1;
1731
		[self setUpdatedProgress:YES];
1732
	}
1733
	if (!value) {
1734
		return;
1735
	}
1736

    
1737
	[ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self];
1738
	[ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&downloadProgressDelegate withObject:self amount:&value callerToRetain:self];
1739

    
1740
	[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self totalBytesRead]+[self partialDownloadSize] ofTotal:[self contentLength]+[self partialDownloadSize]];
1741

    
1742
	#if NS_BLOCKS_AVAILABLE
1743
    if (bytesReceivedBlock) {
1744
		unsigned long long totalSize = [self contentLength] + [self partialDownloadSize];
1745
		[self performBlockOnMainThread:^{ if (bytesReceivedBlock) { bytesReceivedBlock(value, totalSize); }}];
1746
    }
1747
	#endif
1748
	[self setLastBytesRead:bytesReadSoFar];
1749
}
1750

    
1751
- (void)updateUploadProgress
1752
{
1753
	if ([self isCancelled] || [self totalBytesSent] == 0) {
1754
		return;
1755
	}
1756
	
1757
	// 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)
1758
	// If request body is less than the buffer size, totalBytesSent will be the total size of the request body
1759
	// We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written
1760
	if ([self uploadBufferSize] == 0 && [self totalBytesSent] != [self postLength]) {
1761
		[self setUploadBufferSize:[self totalBytesSent]];
1762
		[self incrementUploadSizeBy:-[self uploadBufferSize]];
1763
	}
1764
	
1765
	unsigned long long value = 0;
1766
	
1767
	if ([self showAccurateProgress]) {
1768
		if ([self totalBytesSent] == [self postLength] || [self lastBytesSent] > 0) {
1769
			value = [self totalBytesSent]-[self lastBytesSent];
1770
		} else {
1771
			return;
1772
		}
1773
	} else {
1774
		value = 1;
1775
		[self setUpdatedProgress:YES];
1776
	}
1777
	
1778
	if (!value) {
1779
		return;
1780
	}
1781
	
1782
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self];
1783
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&value callerToRetain:self];
1784
	[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self totalBytesSent]-[self uploadBufferSize] ofTotal:[self postLength]-[self uploadBufferSize]];
1785

    
1786
	#if NS_BLOCKS_AVAILABLE
1787
    if(bytesSentBlock){
1788
		unsigned long long totalSize = [self postLength];
1789
		[self performBlockOnMainThread:^{ if (bytesSentBlock) { bytesSentBlock(value, totalSize); }}];
1790
	}
1791
	#endif
1792
}
1793

    
1794

    
1795
- (void)incrementDownloadSizeBy:(long long)length
1796
{
1797
	[ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self];
1798
	[ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&downloadProgressDelegate withObject:self amount:&length callerToRetain:self];
1799

    
1800
	#if NS_BLOCKS_AVAILABLE
1801
    if(downloadSizeIncrementedBlock){
1802
		[self performBlockOnMainThread:^{ if (downloadSizeIncrementedBlock) { downloadSizeIncrementedBlock(length); }}];
1803
    }
1804
	#endif
1805
}
1806

    
1807
- (void)incrementUploadSizeBy:(long long)length
1808
{
1809
	[ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self];
1810
	[ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&uploadProgressDelegate withObject:self amount:&length callerToRetain:self];
1811

    
1812
	#if NS_BLOCKS_AVAILABLE
1813
    if(uploadSizeIncrementedBlock) {
1814
		[self performBlockOnMainThread:^{ if (uploadSizeIncrementedBlock) { uploadSizeIncrementedBlock(length); }}];
1815
    }
1816
	#endif
1817
}
1818

    
1819

    
1820
-(void)removeUploadProgressSoFar
1821
{
1822
	long long progressToRemove = -[self totalBytesSent];
1823
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&progressToRemove callerToRetain:self];
1824
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&progressToRemove callerToRetain:self];
1825
	[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:[self postLength]];
1826

    
1827
	#if NS_BLOCKS_AVAILABLE
1828
    if(bytesSentBlock){
1829
		unsigned long long totalSize = [self postLength];
1830
		[self performBlockOnMainThread:^{  if (bytesSentBlock) { bytesSentBlock(progressToRemove, totalSize); }}];
1831
	}
1832
	#endif
1833
}
1834

    
1835
#if NS_BLOCKS_AVAILABLE
1836
- (void)performBlockOnMainThread:(ASIBasicBlock)block
1837
{
1838
	[self performSelectorOnMainThread:@selector(callBlock:) withObject:[[block copy] autorelease] waitUntilDone:[NSThread isMainThread]];
1839
}
1840

    
1841
- (void)callBlock:(ASIBasicBlock)block
1842
{
1843
	block();
1844
}
1845
#endif
1846

    
1847

    
1848
+ (void)performSelector:(SEL)selector onTarget:(id *)target withObject:(id)object amount:(void *)amount callerToRetain:(id)callerToRetain
1849
{
1850
	if ([*target respondsToSelector:selector]) {
1851
		NSMethodSignature *signature = nil;
1852
		signature = [*target methodSignatureForSelector:selector];
1853
		NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
1854

    
1855
		[invocation setSelector:selector];
1856
		
1857
		int argumentNumber = 2;
1858
		
1859
		// If we got an object parameter, we pass a pointer to the object pointer
1860
		if (object) {
1861
			[invocation setArgument:&object atIndex:argumentNumber];
1862
			argumentNumber++;
1863
		}
1864
		
1865
		// 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
1866
		if (amount) {
1867
			[invocation setArgument:amount atIndex:argumentNumber];
1868
		}
1869

    
1870
        SEL callback = @selector(performInvocation:onTarget:releasingObject:);
1871
        NSMethodSignature *cbSignature = [ASIHTTPRequest methodSignatureForSelector:callback];
1872
        NSInvocation *cbInvocation = [NSInvocation invocationWithMethodSignature:cbSignature];
1873
        [cbInvocation setSelector:callback];
1874
        [cbInvocation setTarget:self];
1875
        [cbInvocation setArgument:&invocation atIndex:2];
1876
        [cbInvocation setArgument:&target atIndex:3];
1877
		if (callerToRetain) {
1878
			[cbInvocation setArgument:&callerToRetain atIndex:4];
1879
		}
1880

    
1881
		CFRetain(invocation);
1882

    
1883
		// Used to pass in a request that we must retain until after the call
1884
		// We're using CFRetain rather than [callerToRetain retain] so things to avoid earthquakes when using garbage collection
1885
		if (callerToRetain) {
1886
			CFRetain(callerToRetain);
1887
		}
1888
        [cbInvocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:[NSThread isMainThread]];
1889
    }
1890
}
1891

    
1892
+ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease
1893
{
1894
    if (*target && [*target respondsToSelector:[invocation selector]]) {
1895
        [invocation invokeWithTarget:*target];
1896
    }
1897
	CFRelease(invocation);
1898
	if (objectToRelease) {
1899
		CFRelease(objectToRelease);
1900
	}
1901
}
1902
	
1903
	
1904
+ (void)updateProgressIndicator:(id *)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total
1905
{
1906
	#if TARGET_OS_IPHONE
1907
		// Cocoa Touch: UIProgressView
1908
		SEL selector = @selector(setProgress:);
1909
		float progressAmount = (float)((progress*1.0)/(total*1.0));
1910
		
1911
	#else
1912
		// Cocoa: NSProgressIndicator
1913
		double progressAmount = progressAmount = (progress*1.0)/(total*1.0);
1914
		SEL selector = @selector(setDoubleValue:);
1915
	#endif
1916
	
1917
	if (![*indicator respondsToSelector:selector]) {
1918
		return;
1919
	}
1920
	
1921
	[progressLock lock];
1922
	[ASIHTTPRequest performSelector:selector onTarget:indicator withObject:nil amount:&progressAmount callerToRetain:nil];
1923
	[progressLock unlock];
1924
}
1925

    
1926

    
1927
#pragma mark talking to delegates / calling blocks
1928

    
1929
/* ALWAYS CALLED ON MAIN THREAD! */
1930
- (void)requestStarted
1931
{
1932
	if ([self error] || [self mainRequest]) {
1933
		return;
1934
	}
1935
	if (delegate && [delegate respondsToSelector:didStartSelector]) {
1936
		[delegate performSelector:didStartSelector withObject:self];
1937
	}
1938
	#if NS_BLOCKS_AVAILABLE
1939
	if(startedBlock){
1940
		startedBlock();
1941
	}
1942
	#endif
1943
	if (queue && [queue respondsToSelector:@selector(requestStarted:)]) {
1944
		[queue performSelector:@selector(requestStarted:) withObject:self];
1945
	}
1946
}
1947

    
1948
/* ALWAYS CALLED ON MAIN THREAD! */
1949
- (void)requestRedirected
1950
{
1951
	if ([self error] || [self mainRequest]) {
1952
		return;
1953
	}
1954

    
1955
	if([[self delegate] respondsToSelector:@selector(requestRedirected:)]){
1956
		[[self delegate] performSelector:@selector(requestRedirected:) withObject:self];
1957
	}
1958

    
1959
	#if NS_BLOCKS_AVAILABLE
1960
	if(requestRedirectedBlock){
1961
		requestRedirectedBlock();
1962
	}
1963
	#endif
1964
}
1965

    
1966

    
1967
/* ALWAYS CALLED ON MAIN THREAD! */
1968
- (void)requestReceivedResponseHeaders:(NSMutableDictionary *)newResponseHeaders
1969
{
1970
	if ([self error] || [self mainRequest]) {
1971
		return;
1972
	}
1973

    
1974
	if (delegate && [delegate respondsToSelector:didReceiveResponseHeadersSelector]) {
1975
		[delegate performSelector:didReceiveResponseHeadersSelector withObject:self withObject:newResponseHeaders];
1976
	}
1977

    
1978
	#if NS_BLOCKS_AVAILABLE
1979
	if(headersReceivedBlock){
1980
		headersReceivedBlock(newResponseHeaders);
1981
    }
1982
	#endif
1983

    
1984
	if (queue && [queue respondsToSelector:@selector(request:didReceiveResponseHeaders:)]) {
1985
		[queue performSelector:@selector(request:didReceiveResponseHeaders:) withObject:self withObject:newResponseHeaders];
1986
	}
1987
}
1988

    
1989
/* ALWAYS CALLED ON MAIN THREAD! */
1990
- (void)requestWillRedirectToURL:(NSURL *)newURL
1991
{
1992
	if ([self error] || [self mainRequest]) {
1993
		return;
1994
	}
1995
	if (delegate && [delegate respondsToSelector:willRedirectSelector]) {
1996
		[delegate performSelector:willRedirectSelector withObject:self withObject:newURL];
1997
	}
1998
	if (queue && [queue respondsToSelector:@selector(request:willRedirectToURL:)]) {
1999
		[queue performSelector:@selector(request:willRedirectToURL:) withObject:self withObject:newURL];
2000
	}
2001
}
2002

    
2003
// Subclasses might override this method to process the result in the same thread
2004
// If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done
2005
- (void)requestFinished
2006
{
2007
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
2008
	ASI_DEBUG_LOG(@"[STATUS] Request finished: %@",self);
2009
#endif
2010
	if ([self error] || [self mainRequest]) {
2011
		return;
2012
	}
2013
	if ([self isPACFileRequest]) {
2014
		[self reportFinished];
2015
	} else {
2016
		[self performSelectorOnMainThread:@selector(reportFinished) withObject:nil waitUntilDone:[NSThread isMainThread]];
2017
	}
2018
}
2019

    
2020
/* ALWAYS CALLED ON MAIN THREAD! */
2021
- (void)reportFinished
2022
{
2023
	if (delegate && [delegate respondsToSelector:didFinishSelector]) {
2024
		[delegate performSelector:didFinishSelector withObject:self];
2025
	}
2026

    
2027
	#if NS_BLOCKS_AVAILABLE
2028
	if(completionBlock){
2029
		completionBlock();
2030
	}
2031
	#endif
2032

    
2033
	if (queue && [queue respondsToSelector:@selector(requestFinished:)]) {
2034
		[queue performSelector:@selector(requestFinished:) withObject:self];
2035
	}
2036
}
2037

    
2038
/* ALWAYS CALLED ON MAIN THREAD! */
2039
- (void)reportFailure
2040
{
2041
	if (delegate && [delegate respondsToSelector:didFailSelector]) {
2042
		[delegate performSelector:didFailSelector withObject:self];
2043
	}
2044

    
2045
	#if NS_BLOCKS_AVAILABLE
2046
    if(failureBlock){
2047
        failureBlock();
2048
    }
2049
	#endif
2050

    
2051
	if (queue && [queue respondsToSelector:@selector(requestFailed:)]) {
2052
		[queue performSelector:@selector(requestFailed:) withObject:self];
2053
	}
2054
}
2055

    
2056
/* ALWAYS CALLED ON MAIN THREAD! */
2057
- (void)passOnReceivedData:(NSData *)data
2058
{
2059
	if (delegate && [delegate respondsToSelector:didReceiveDataSelector]) {
2060
		[delegate performSelector:didReceiveDataSelector withObject:self withObject:data];
2061
	}
2062

    
2063
	#if NS_BLOCKS_AVAILABLE
2064
	if (dataReceivedBlock) {
2065
		dataReceivedBlock(data);
2066
	}
2067
	#endif
2068
}
2069

    
2070
// Subclasses might override this method to perform error handling in the same thread
2071
// If you do this, don't forget to call [super failWithError:] to let the queue / delegate know we're done
2072
- (void)failWithError:(NSError *)theError
2073
{
2074
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
2075
	ASI_DEBUG_LOG(@"[STATUS] Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed"));
2076
#endif
2077
	[self setComplete:YES];
2078
	
2079
	// Invalidate the current connection so subsequent requests don't attempt to reuse it
2080
	if (theError && [theError code] != ASIAuthenticationErrorType && [theError code] != ASITooMuchRedirectionErrorType) {
2081
		[connectionsLock lock];
2082
		#if DEBUG_PERSISTENT_CONNECTIONS
2083
		ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ failed and will invalidate connection #%@",[self requestID],[[self connectionInfo] objectForKey:@"id"]);
2084
		#endif
2085
		[[self connectionInfo] removeObjectForKey:@"request"];
2086
		[persistentConnectionsPool removeObject:[self connectionInfo]];
2087
		[connectionsLock unlock];
2088
		[self destroyReadStream];
2089
	}
2090
	if ([self connectionCanBeReused]) {
2091
		[[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"];
2092
	}
2093
	
2094
    if ([self isCancelled] || [self error]) {
2095
		return;
2096
	}
2097
	
2098
	// If we have cached data, use it and ignore the error when using ASIFallbackToCacheIfLoadFailsCachePolicy
2099
	if ([self downloadCache] && ([self cachePolicy] & ASIFallbackToCacheIfLoadFailsCachePolicy)) {
2100
		if ([[self downloadCache] canUseCachedDataForRequest:self]) {
2101
			[self useDataFromCache];
2102
			return;
2103
		}
2104
	}
2105
	
2106
	
2107
	[self setError:theError];
2108
	
2109
	ASIHTTPRequest *failedRequest = self;
2110
	
2111
	// If this is a HEAD request created by an ASINetworkQueue or compatible queue delegate, make the main request fail
2112
	if ([self mainRequest]) {
2113
		failedRequest = [self mainRequest];
2114
		[failedRequest setError:theError];
2115
	}
2116

    
2117
	if ([self isPACFileRequest]) {
2118
		[failedRequest reportFailure];
2119
	} else {
2120
		[failedRequest performSelectorOnMainThread:@selector(reportFailure) withObject:nil waitUntilDone:[NSThread isMainThread]];
2121
	}
2122
	
2123
    if (!inProgress)
2124
    {
2125
        // if we're not in progress, we can't notify the queue we've finished (doing so can cause a crash later on)
2126
        // "markAsFinished" will be at the start of main() when we are started
2127
        return;
2128
    }
2129
	[self markAsFinished];
2130
}
2131

    
2132
#pragma mark parsing HTTP response headers
2133

    
2134
- (void)readResponseHeaders
2135
{
2136
	[self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
2137

    
2138
	CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPResponseHeader);
2139
	if (!message) {
2140
		return;
2141
	}
2142
	
2143
	// Make sure we've received all the headers
2144
	if (!CFHTTPMessageIsHeaderComplete(message)) {
2145
		CFRelease(message);
2146
		return;
2147
	}
2148

    
2149
	#if DEBUG_REQUEST_STATUS
2150
	if ([self totalBytesSent] == [self postLength]) {
2151
		ASI_DEBUG_LOG(@"[STATUS] Request %@ received response headers",self);
2152
	}
2153
	#endif		
2154

    
2155
	[self setResponseHeaders:[NSMakeCollectable(CFHTTPMessageCopyAllHeaderFields(message)) autorelease]];
2156
	[self setResponseStatusCode:(int)CFHTTPMessageGetResponseStatusCode(message)];
2157
	[self setResponseStatusMessage:[NSMakeCollectable(CFHTTPMessageCopyResponseStatusLine(message)) autorelease]];
2158

    
2159
	if ([self downloadCache] && ([[self downloadCache] canUseCachedDataForRequest:self])) {
2160

    
2161
		// Update the expiry date
2162
		[[self downloadCache] updateExpiryForRequest:self maxAge:[self secondsToCache]];
2163

    
2164
		// Read the response from the cache
2165
		[self useDataFromCache];
2166

    
2167
		CFRelease(message);
2168
		return;
2169
	}
2170

    
2171
	// Is the server response a challenge for credentials?
2172
	if ([self responseStatusCode] == 401) {
2173
		[self setAuthenticationNeeded:ASIHTTPAuthenticationNeeded];
2174
	} else if ([self responseStatusCode] == 407) {
2175
		[self setAuthenticationNeeded:ASIProxyAuthenticationNeeded];
2176
	} else {
2177
		#if DEBUG_HTTP_AUTHENTICATION
2178
		if ([self authenticationScheme]) {
2179
			ASI_DEBUG_LOG(@"[AUTH] Request %@ has passed %@ authentication",self,[self authenticationScheme]);
2180
		}
2181
		#endif
2182
	}
2183
		
2184
	// Authentication succeeded, or no authentication was required
2185
	if (![self authenticationNeeded]) {
2186

    
2187
		// Did we get here without an authentication challenge? (which can happen when shouldPresentCredentialsBeforeChallenge is YES and basic auth was successful)
2188
		if (!requestAuthentication && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic] && [self username] && [self password] && [self useSessionPersistence]) {
2189

    
2190
			#if DEBUG_HTTP_AUTHENTICATION
2191
			ASI_DEBUG_LOG(@"[AUTH] Request %@ passed BASIC authentication, and will save credentials in the session store for future use",self);
2192
			#endif
2193
			
2194
			NSMutableDictionary *newCredentials = [NSMutableDictionary dictionaryWithCapacity:2];
2195
			[newCredentials setObject:[self username] forKey:(NSString *)kCFHTTPAuthenticationUsername];
2196
			[newCredentials setObject:[self password] forKey:(NSString *)kCFHTTPAuthenticationPassword];
2197
			
2198
			// Store the credentials in the session 
2199
			NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary];
2200
			[sessionCredentials setObject:newCredentials forKey:@"Credentials"];
2201
			[sessionCredentials setObject:[self url] forKey:@"URL"];
2202
			[sessionCredentials setObject:(NSString *)kCFHTTPAuthenticationSchemeBasic forKey:@"AuthenticationScheme"];
2203
			[[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials];
2204
		}
2205
	}
2206

    
2207
	// Read response textEncoding
2208
	[self parseStringEncodingFromHeaders];
2209

    
2210
	// Handle cookies
2211
	NSArray *newCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[self responseHeaders] forURL:[self url]];
2212
	[self setResponseCookies:newCookies];
2213
	
2214
	if ([self useCookiePersistence]) {
2215
		
2216
		// Store cookies in global persistent store
2217
		[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:newCookies forURL:[self url] mainDocumentURL:nil];
2218
		
2219
		// We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later
2220
		NSHTTPCookie *cookie;
2221
		for (cookie in newCookies) {
2222
			[ASIHTTPRequest addSessionCookie:cookie];
2223
		}
2224
	}
2225
	
2226
	// Do we need to redirect?
2227
	if (![self willRedirect]) {
2228
		// See if we got a Content-length header
2229
		NSString *cLength = [responseHeaders valueForKey:@"Content-Length"];
2230
		ASIHTTPRequest *theRequest = self;
2231
		if ([self mainRequest]) {
2232
			theRequest = [self mainRequest];
2233
		}
2234

    
2235
		if (cLength) {
2236
			unsigned long long length = strtoull([cLength UTF8String], NULL, 0);
2237

    
2238
			// Workaround for Apache HEAD requests for dynamically generated content returning the wrong Content-Length when using gzip
2239
			if ([self mainRequest] && [self allowCompressedResponse] && length == 20 && [self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2240
				[[self mainRequest] setShowAccurateProgress:NO];
2241
				[[self mainRequest] incrementDownloadSizeBy:1];
2242

    
2243
			} else {
2244
				[theRequest setContentLength:length];
2245
				if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2246
					[theRequest incrementDownloadSizeBy:[theRequest contentLength]+[theRequest partialDownloadSize]];
2247
				}
2248
			}
2249

    
2250
		} else if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2251
			[theRequest setShowAccurateProgress:NO];
2252
			[theRequest incrementDownloadSizeBy:1];
2253
		}
2254
	}
2255

    
2256
	// Handle connection persistence
2257
	if ([self shouldAttemptPersistentConnection]) {
2258
		
2259
		NSString *connectionHeader = [[[self responseHeaders] objectForKey:@"Connection"] lowercaseString];
2260

    
2261
		NSString *httpVersion = [NSMakeCollectable(CFHTTPMessageCopyVersion(message)) autorelease];
2262
		
2263
		// Don't re-use the connection if the server is HTTP 1.0 and didn't send Connection: Keep-Alive
2264
		if (![httpVersion isEqualToString:(NSString *)kCFHTTPVersion1_0] || [connectionHeader isEqualToString:@"keep-alive"]) {
2265

    
2266
			// See if server explicitly told us to close the connection
2267
			if (![connectionHeader isEqualToString:@"close"]) {
2268
				
2269
				NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"];
2270
				
2271
				// If we got a keep alive header, we'll reuse the connection for as long as the server tells us
2272
				if (keepAliveHeader) { 
2273
					int timeout = 0;
2274
					int max = 0;
2275
					NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader];
2276
					[scanner scanString:@"timeout=" intoString:NULL];
2277
					[scanner scanInt:&timeout];
2278
					[scanner scanUpToString:@"max=" intoString:NULL];
2279
					[scanner scanString:@"max=" intoString:NULL];
2280
					[scanner scanInt:&max];
2281
					if (max > 5) {
2282
						[self setConnectionCanBeReused:YES];
2283
						[self setPersistentConnectionTimeoutSeconds:timeout];
2284
						#if DEBUG_PERSISTENT_CONNECTIONS
2285
							ASI_DEBUG_LOG(@"[CONNECTION] Got a keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]);
2286
						#endif					
2287
					}
2288
				
2289
				// Otherwise, we'll assume we can keep this connection open
2290
				} else {
2291
					[self setConnectionCanBeReused:YES];
2292
					#if DEBUG_PERSISTENT_CONNECTIONS
2293
						ASI_DEBUG_LOG(@"[CONNECTION] Got no keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]);
2294
					#endif
2295
				}
2296
			}
2297
		}
2298
	}
2299

    
2300
	CFRelease(message);
2301
	[self performSelectorOnMainThread:@selector(requestReceivedResponseHeaders:) withObject:[[[self responseHeaders] copy] autorelease] waitUntilDone:[NSThread isMainThread]];
2302
}
2303

    
2304
- (BOOL)willRedirect
2305
{
2306
	// Do we need to redirect?
2307
	if (![self shouldRedirect] || ![responseHeaders valueForKey:@"Location"]) {
2308
		return NO;
2309
	}
2310

    
2311
	// Note that ASIHTTPRequest does not currently support 305 Use Proxy
2312
	int responseCode = [self responseStatusCode];
2313
	if (responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) {
2314
		return NO;
2315
	}
2316

    
2317
	[self performSelectorOnMainThread:@selector(requestRedirected) withObject:nil waitUntilDone:[NSThread isMainThread]];
2318

    
2319
	// By default, we redirect 301 and 302 response codes as GET requests
2320
	// According to RFC 2616 this is wrong, but this is what most browsers do, so it's probably what you're expecting to happen
2321
	// See also:
2322
	// http://allseeing-i.lighthouseapp.com/projects/27881/tickets/27-302-redirection-issue
2323

    
2324
	if (responseCode != 307 && (![self shouldUseRFC2616RedirectBehaviour] || responseCode == 303)) {
2325
		[self setRequestMethod:@"GET"];
2326
		[self setPostBody:nil];
2327
		[self setPostLength:0];
2328

    
2329
		// 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.
2330
		NSString *userAgentHeader = [[self requestHeaders] objectForKey:@"User-Agent"];
2331
		NSString *acceptHeader = [[self requestHeaders] objectForKey:@"Accept"];
2332
		[self setRequestHeaders:nil];
2333
		if (userAgentHeader) {
2334
			[self addRequestHeader:@"User-Agent" value:userAgentHeader];
2335
		}
2336
		if (acceptHeader) {
2337
			[self addRequestHeader:@"Accept" value:acceptHeader];
2338
		}
2339
		[self setHaveBuiltRequestHeaders:NO];
2340

    
2341
	} else {
2342
		// Force rebuild the cookie header incase we got some new cookies from this request
2343
		// All other request headers will remain as they are for 301 / 302 redirects
2344
		[self applyCookieHeader];
2345
	}
2346

    
2347
	// Force the redirected request to rebuild the request headers (if not a 303, it will re-use old ones, and add any new ones)
2348
	[self setRedirectURL:[[NSURL URLWithString:[responseHeaders valueForKey:@"Location"] relativeToURL:[self url]] absoluteURL]];
2349
	[self setNeedsRedirect:YES];
2350

    
2351
	// Clear the request cookies
2352
	// This means manually added cookies will not be added to the redirect request - only those stored in the global persistent store
2353
	// But, this is probably the safest option - we might be redirecting to a different domain
2354
	[self setRequestCookies:[NSMutableArray array]];
2355

    
2356
	#if DEBUG_REQUEST_STATUS
2357
	ASI_DEBUG_LOG(@"[STATUS] Request will redirect (code: %i): %@",responseCode,self);
2358
	#endif
2359

    
2360
	return YES;
2361
}
2362

    
2363
- (void)parseStringEncodingFromHeaders
2364
{
2365
	// Handle response text encoding
2366
	NSStringEncoding charset = 0;
2367
	NSString *mimeType = nil;
2368
	[[self class] parseMimeType:&mimeType andResponseEncoding:&charset fromContentType:[[self responseHeaders] valueForKey:@"Content-Type"]];
2369
	if (charset != 0) {
2370
		[self setResponseEncoding:charset];
2371
	} else {
2372
		[self setResponseEncoding:[self defaultResponseEncoding]];
2373
	}
2374
}
2375

    
2376
#pragma mark http authentication
2377

    
2378
- (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials
2379
{
2380
	NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
2381
	if (authenticationCredentials) {
2382
		[ASIHTTPRequest saveCredentials:authenticationCredentials forProxy:[self proxyHost] port:[self proxyPort] realm:[self proxyAuthenticationRealm]];
2383
	}	
2384
}
2385

    
2386

    
2387
- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials
2388
{
2389
	NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
2390
	
2391
	if (authenticationCredentials) {
2392
		[ASIHTTPRequest saveCredentials:authenticationCredentials forHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
2393
	}	
2394
}
2395

    
2396
- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials
2397
{
2398
	[self setProxyAuthenticationRetryCount:[self proxyAuthenticationRetryCount]+1];
2399
	
2400
	if (newCredentials && proxyAuthentication && request) {
2401

    
2402
		// Apply whatever credentials we've built up to the old request
2403
		if (CFHTTPMessageApplyCredentialDictionary(request, proxyAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
2404
			
2405
			//If we have credentials and they're ok, let's save them to the keychain
2406
			if (useKeychainPersistence) {
2407
				[self saveProxyCredentialsToKeychain:newCredentials];
2408
			}
2409
			if (useSessionPersistence) {
2410
				NSMutableDictionary *sessionProxyCredentials = [NSMutableDictionary dictionary];
2411
				[sessionProxyCredentials setObject:(id)proxyAuthentication forKey:@"Authentication"];
2412
				[sessionProxyCredentials setObject:newCredentials forKey:@"Credentials"];
2413
				[sessionProxyCredentials setObject:[self proxyHost] forKey:@"Host"];
2414
				[sessionProxyCredentials setObject:[NSNumber numberWithInt:[self proxyPort]] forKey:@"Port"];
2415
				[sessionProxyCredentials setObject:[self proxyAuthenticationScheme] forKey:@"AuthenticationScheme"];
2416
				[[self class] storeProxyAuthenticationCredentialsInSessionStore:sessionProxyCredentials];
2417
			}
2418
			[self setProxyCredentials:newCredentials];
2419
			return YES;
2420
		} else {
2421
			[[self class] removeProxyAuthenticationCredentialsFromSessionStore:newCredentials];
2422
		}
2423
	}
2424
	return NO;
2425
}
2426

    
2427
- (BOOL)applyCredentials:(NSDictionary *)newCredentials
2428
{
2429
	[self setAuthenticationRetryCount:[self authenticationRetryCount]+1];
2430
	
2431
	if (newCredentials && requestAuthentication && request) {
2432
		// Apply whatever credentials we've built up to the old request
2433
		if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
2434
			
2435
			//If we have credentials and they're ok, let's save them to the keychain
2436
			if (useKeychainPersistence) {
2437
				[self saveCredentialsToKeychain:newCredentials];
2438
			}
2439
			if (useSessionPersistence) {
2440
				
2441
				NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary];
2442
				[sessionCredentials setObject:(id)requestAuthentication forKey:@"Authentication"];
2443
				[sessionCredentials setObject:newCredentials forKey:@"Credentials"];
2444
				[sessionCredentials setObject:[self url] forKey:@"URL"];
2445
				[sessionCredentials setObject:[self authenticationScheme] forKey:@"AuthenticationScheme"];
2446
				if ([self authenticationRealm]) {
2447
					[sessionCredentials setObject:[self authenticationRealm] forKey:@"AuthenticationRealm"];
2448
				}
2449
				[[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials];
2450

    
2451
			}
2452
			[self setRequestCredentials:newCredentials];
2453
			return YES;
2454
		} else {
2455
			[[self class] removeAuthenticationCredentialsFromSessionStore:newCredentials];
2456
		}
2457
	}
2458
	return NO;
2459
}
2460

    
2461
- (NSMutableDictionary *)findProxyCredentials
2462
{
2463
	NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
2464
	
2465
	NSString *user = nil;
2466
	NSString *pass = nil;
2467
	
2468
	ASIHTTPRequest *theRequest = [self mainRequest];
2469
	// If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
2470
	if ([theRequest proxyUsername] && [theRequest proxyPassword]) {
2471
		user = [theRequest proxyUsername];
2472
		pass = [theRequest proxyPassword];
2473
		
2474
	// Let's try to use the ones set in this object
2475
	} else if ([self proxyUsername] && [self proxyPassword]) {
2476
		user = [self proxyUsername];
2477
		pass = [self proxyPassword];
2478
	}
2479

    
2480
	// When we connect to a website using NTLM via a proxy, we will use the main credentials
2481
	if ((!user || !pass) && [self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM) {
2482
		user = [self username];
2483
		pass = [self password];
2484
	}
2485

    
2486

    
2487
	
2488
	// Ok, that didn't work, let's try the keychain
2489
	// For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence
2490
	if ((!user || !pass)) {
2491
		NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForProxy:[self proxyHost] port:[self proxyPort] protocol:[[self url] scheme] realm:[self proxyAuthenticationRealm]];
2492
		if (authenticationCredentials) {
2493
			user = [authenticationCredentials user];
2494
			pass = [authenticationCredentials password];
2495
		}
2496
		
2497
	}
2498

    
2499
	// Handle NTLM, which requires a domain to be set too
2500
	if (CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) {
2501

    
2502
		NSString *ntlmDomain = [self proxyDomain];
2503

    
2504
		// If we have no domain yet
2505
		if (!ntlmDomain || [ntlmDomain length] == 0) {
2506

    
2507
			// Let's try to extract it from the username
2508
			NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"];
2509
			if ([ntlmComponents count] == 2) {
2510
				ntlmDomain = [ntlmComponents objectAtIndex:0];
2511
				user = [ntlmComponents objectAtIndex:1];
2512

    
2513
			// If we are connecting to a website using NTLM, but we are connecting via a proxy, the string we need may be in the domain property
2514
			} else {
2515
				ntlmDomain = [self domain];
2516
			}
2517
			if (!ntlmDomain) {
2518
				ntlmDomain = @"";
2519
			}
2520
		}
2521
		[newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
2522
	}
2523

    
2524

    
2525
	// If we have a username and password, let's apply them to the request and continue
2526
	if (user && pass) {
2527
		[newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
2528
		[newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
2529
		return newCredentials;
2530
	}
2531
	return nil;
2532
}
2533

    
2534

    
2535
- (NSMutableDictionary *)findCredentials
2536
{
2537
	NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
2538

    
2539
	// First, let's look at the url to see if the username and password were included
2540
	NSString *user = [[self url] user];
2541
	NSString *pass = [[self url] password];
2542

    
2543
	if (user && pass) {
2544

    
2545
		#if DEBUG_HTTP_AUTHENTICATION
2546
		ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials set on its url",self);
2547
		#endif
2548

    
2549
	} else {
2550
		
2551
		// If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
2552
		if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) {
2553
			user = [[self mainRequest] username];
2554
			pass = [[self mainRequest] password];
2555

    
2556
			#if DEBUG_HTTP_AUTHENTICATION
2557
			ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials from its parent request",self);
2558
			#endif
2559

    
2560
		// Let's try to use the ones set in this object
2561
		} else if ([self username] && [self password]) {
2562
			user = [self username];
2563
			pass = [self password];
2564

    
2565
			#if DEBUG_HTTP_AUTHENTICATION
2566
			ASI_DEBUG_LOG(@"[AUTH] Request %@ will use username and password properties as credentials",self);
2567
			#endif
2568
		}		
2569
	}
2570
	
2571
	// Ok, that didn't work, let's try the keychain
2572
	if ((!user || !pass) && useKeychainPersistence) {
2573
		NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
2574
		if (authenticationCredentials) {
2575
			user = [authenticationCredentials user];
2576
			pass = [authenticationCredentials password];
2577
			#if DEBUG_HTTP_AUTHENTICATION
2578
			if (user && pass) {
2579
				ASI_DEBUG_LOG(@"[AUTH] Request %@ will use credentials from the keychain",self);
2580
			}
2581
			#endif
2582
		}
2583
	}
2584

    
2585
	// Handle NTLM, which requires a domain to be set too
2586
	if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
2587

    
2588
		NSString *ntlmDomain = [self domain];
2589

    
2590
		// If we have no domain yet, let's try to extract it from the username
2591
		if (!ntlmDomain || [ntlmDomain length] == 0) {
2592
			ntlmDomain = @"";
2593
			NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"];
2594
			if ([ntlmComponents count] == 2) {
2595
				ntlmDomain = [ntlmComponents objectAtIndex:0];
2596
				user = [ntlmComponents objectAtIndex:1];
2597
			}
2598
		}
2599
		[newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
2600
	}
2601

    
2602
	// If we have a username and password, let's apply them to the request and continue
2603
	if (user && pass) {
2604
		[newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
2605
		[newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
2606
		return newCredentials;
2607
	}
2608
	return nil;
2609
}
2610

    
2611
// Called by delegate or authentication dialog to resume loading once authentication info has been populated
2612
- (void)retryUsingSuppliedCredentials
2613
{
2614
	#if DEBUG_HTTP_AUTHENTICATION
2615
		ASI_DEBUG_LOG(@"[AUTH] Request %@ received credentials from its delegate or an ASIAuthenticationDialog, will retry",self);
2616
	#endif
2617
	//If the url was changed by the delegate, our CFHTTPMessageRef will be NULL and we'll go back to the start
2618
	if (!request) {
2619
		[self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2620
		return;
2621
	}
2622
	[self performSelector:@selector(attemptToApplyCredentialsAndResume) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2623
}
2624

    
2625
// Called by delegate or authentication dialog to cancel authentication
2626
- (void)cancelAuthentication
2627
{
2628
	#if DEBUG_HTTP_AUTHENTICATION
2629
		ASI_DEBUG_LOG(@"[AUTH] Request %@ had authentication cancelled by its delegate or an ASIAuthenticationDialog",self);
2630
	#endif
2631
	[self performSelector:@selector(failAuthentication) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2632
}
2633

    
2634
- (void)failAuthentication
2635
{
2636
	[self failWithError:ASIAuthenticationError];
2637
}
2638

    
2639
- (BOOL)showProxyAuthenticationDialog
2640
{
2641
	if ([self isSynchronous]) {
2642
		return NO;
2643
	}
2644

    
2645
	// Mac authentication dialog coming soon!
2646
	#if TARGET_OS_IPHONE
2647
	if ([self shouldPresentProxyAuthenticationDialog]) {
2648
		[ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
2649
		return YES;
2650
	}
2651
	return NO;
2652
	#else
2653
	return NO;
2654
	#endif
2655
}
2656

    
2657

    
2658
- (BOOL)willAskDelegateForProxyCredentials
2659
{
2660
	if ([self isSynchronous]) {
2661
		return NO;
2662
	}
2663

    
2664
	// If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
2665
	// Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
2666
	id authenticationDelegate = [self delegate];
2667
	if (!authenticationDelegate) {
2668
		authenticationDelegate = [self queue];
2669
	}
2670
	
2671
	BOOL delegateOrBlockWillHandleAuthentication = NO;
2672

    
2673
	if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
2674
		delegateOrBlockWillHandleAuthentication = YES;
2675
	}
2676

    
2677
	#if NS_BLOCKS_AVAILABLE
2678
	if(proxyAuthenticationNeededBlock){
2679
		delegateOrBlockWillHandleAuthentication = YES;
2680
	}
2681
	#endif
2682

    
2683
	if (delegateOrBlockWillHandleAuthentication) {
2684
		[self performSelectorOnMainThread:@selector(askDelegateForProxyCredentials) withObject:nil waitUntilDone:NO];
2685
	}
2686
	
2687
	return delegateOrBlockWillHandleAuthentication;
2688
}
2689

    
2690
/* ALWAYS CALLED ON MAIN THREAD! */
2691
- (void)askDelegateForProxyCredentials
2692
{
2693
	id authenticationDelegate = [self delegate];
2694
	if (!authenticationDelegate) {
2695
		authenticationDelegate = [self queue];
2696
	}
2697
	if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
2698
		[authenticationDelegate performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:self];
2699
		return;
2700
	}
2701
	#if NS_BLOCKS_AVAILABLE
2702
	if(proxyAuthenticationNeededBlock){
2703
		proxyAuthenticationNeededBlock();
2704
	}
2705
	#endif
2706
}
2707

    
2708

    
2709
- (BOOL)willAskDelegateForCredentials
2710
{
2711
	if ([self isSynchronous]) {
2712
		return NO;
2713
	}
2714

    
2715
	// If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
2716
	// Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
2717
	id authenticationDelegate = [self delegate];
2718
	if (!authenticationDelegate) {
2719
		authenticationDelegate = [self queue];
2720
	}
2721

    
2722
	BOOL delegateOrBlockWillHandleAuthentication = NO;
2723

    
2724
	if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) {
2725
		delegateOrBlockWillHandleAuthentication = YES;
2726
	}
2727

    
2728
	#if NS_BLOCKS_AVAILABLE
2729
	if (authenticationNeededBlock) {
2730
		delegateOrBlockWillHandleAuthentication = YES;
2731
	}
2732
	#endif
2733

    
2734
	if (delegateOrBlockWillHandleAuthentication) {
2735
		[self performSelectorOnMainThread:@selector(askDelegateForCredentials) withObject:nil waitUntilDone:NO];
2736
	}
2737
	return delegateOrBlockWillHandleAuthentication;
2738
}
2739

    
2740
/* ALWAYS CALLED ON MAIN THREAD! */
2741
- (void)askDelegateForCredentials
2742
{
2743
	id authenticationDelegate = [self delegate];
2744
	if (!authenticationDelegate) {
2745
		authenticationDelegate = [self queue];
2746
	}
2747
	
2748
	if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) {
2749
		[authenticationDelegate performSelector:@selector(authenticationNeededForRequest:) withObject:self];
2750
		return;
2751
	}
2752
	
2753
	#if NS_BLOCKS_AVAILABLE
2754
	if (authenticationNeededBlock) {
2755
		authenticationNeededBlock();
2756
	}
2757
	#endif	
2758
}
2759

    
2760
- (void)attemptToApplyProxyCredentialsAndResume
2761
{
2762
	
2763
	if ([self error] || [self isCancelled]) {
2764
		return;
2765
	}
2766
	
2767
	// Read authentication data
2768
	if (!proxyAuthentication) {
2769
		CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader);
2770
		proxyAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
2771
		CFRelease(responseHeader);
2772
		[self setProxyAuthenticationScheme:[NSMakeCollectable(CFHTTPAuthenticationCopyMethod(proxyAuthentication)) autorelease]];
2773
	}
2774
	
2775
	// If we haven't got a CFHTTPAuthenticationRef by now, something is badly wrong, so we'll have to give up
2776
	if (!proxyAuthentication) {
2777
		[self cancelLoad];
2778
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
2779
		return;
2780
	}
2781
	
2782
	// Get the authentication realm
2783
	[self setProxyAuthenticationRealm:nil];
2784
	if (!CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) {
2785
		[self setProxyAuthenticationRealm:[NSMakeCollectable(CFHTTPAuthenticationCopyRealm(proxyAuthentication)) autorelease]];
2786
	}
2787
	
2788
	// See if authentication is valid
2789
	CFStreamError err;		
2790
	if (!CFHTTPAuthenticationIsValid(proxyAuthentication, &err)) {
2791
		
2792
		CFRelease(proxyAuthentication);
2793
		proxyAuthentication = NULL;
2794
		
2795
		// check for bad credentials, so we can give the delegate a chance to replace them
2796
		if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
2797
			
2798
			// Prevent more than one request from asking for credentials at once
2799
			[delegateAuthenticationLock lock];
2800
			
2801
			// We know the credentials we just presented are bad, we should remove them from the session store too
2802
			[[self class] removeProxyAuthenticationCredentialsFromSessionStore:proxyCredentials];
2803
			[self setProxyCredentials:nil];
2804
			
2805
			
2806
			// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
2807
			if ([self error] || [self isCancelled]) {
2808
				[delegateAuthenticationLock unlock];
2809
				return;
2810
			}
2811
			
2812
			
2813
			// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
2814
			if ([self useSessionPersistence]) {
2815
				NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials];
2816
				if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) {
2817
					[delegateAuthenticationLock unlock];
2818
					[self startRequest];
2819
					return;
2820
				}
2821
			}
2822
			
2823
			[self setLastActivityTime:nil];
2824
			
2825
			if ([self willAskDelegateForProxyCredentials]) {
2826
				[self attemptToApplyProxyCredentialsAndResume];
2827
				[delegateAuthenticationLock unlock];
2828
				return;
2829
			}
2830
			if ([self showProxyAuthenticationDialog]) {
2831
				[self attemptToApplyProxyCredentialsAndResume];
2832
				[delegateAuthenticationLock unlock];
2833
				return;
2834
			}
2835
			[delegateAuthenticationLock unlock];
2836
		}
2837
		[self cancelLoad];
2838
		[self failWithError:ASIAuthenticationError];
2839
		return;
2840
	}
2841

    
2842
	[self cancelLoad];
2843
	
2844
	if (proxyCredentials) {
2845
		
2846
		// We use startRequest rather than starting all over again in load request because NTLM requires we reuse the request
2847
		if ((([self proxyAuthenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self proxyAuthenticationRetryCount] < 2) && [self applyProxyCredentials:proxyCredentials]) {
2848
			[self startRequest];
2849
			
2850
		// We've failed NTLM authentication twice, we should assume our credentials are wrong
2851
		} else if ([self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self proxyAuthenticationRetryCount] == 2) {
2852
			[self failWithError:ASIAuthenticationError];
2853
			
2854
		// Something went wrong, we'll have to give up
2855
		} else {
2856
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]];
2857
		}
2858
		
2859
	// Are a user name & password needed?
2860
	}  else if (CFHTTPAuthenticationRequiresUserNameAndPassword(proxyAuthentication)) {
2861
		
2862
		// Prevent more than one request from asking for credentials at once
2863
		[delegateAuthenticationLock lock];
2864
		
2865
		// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
2866
		if ([self error] || [self isCancelled]) {
2867
			[delegateAuthenticationLock unlock];
2868
			return;
2869
		}
2870
		
2871
		// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
2872
		if ([self useSessionPersistence]) {
2873
			NSDictionary *credentials = [self findSessionProxyAuthenticationCredentials];
2874
			if (credentials && [self applyProxyCredentials:[credentials objectForKey:@"Credentials"]]) {
2875
				[delegateAuthenticationLock unlock];
2876
				[self startRequest];
2877
				return;
2878
			}
2879
		}
2880
		
2881
		NSMutableDictionary *newCredentials = [self findProxyCredentials];
2882
		
2883
		//If we have some credentials to use let's apply them to the request and continue
2884
		if (newCredentials) {
2885
			
2886
			if ([self applyProxyCredentials:newCredentials]) {
2887
				[delegateAuthenticationLock unlock];
2888
				[self startRequest];
2889
			} else {
2890
				[delegateAuthenticationLock unlock];
2891
				[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply proxy credentials to request",NSLocalizedDescriptionKey,nil]]];
2892
			}
2893
			
2894
			return;
2895
		}
2896
		
2897
		if ([self willAskDelegateForProxyCredentials]) {
2898
			[delegateAuthenticationLock unlock];
2899
			return;
2900
		}
2901
		
2902
		if ([self showProxyAuthenticationDialog]) {
2903
			[delegateAuthenticationLock unlock];
2904
			return;
2905
		}
2906
		[delegateAuthenticationLock unlock];
2907
		
2908
		// The delegate isn't interested and we aren't showing the authentication dialog, we'll have to give up
2909
		[self failWithError:ASIAuthenticationError];
2910
		return;
2911
	}
2912
	
2913
}
2914

    
2915
- (BOOL)showAuthenticationDialog
2916
{
2917
	if ([self isSynchronous]) {
2918
		return NO;
2919
	}
2920
	// Mac authentication dialog coming soon!
2921
	#if TARGET_OS_IPHONE
2922
	if ([self shouldPresentAuthenticationDialog]) {
2923
		[ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
2924
		return YES;
2925
	}
2926
	return NO;
2927
	#else
2928
	return NO;
2929
	#endif
2930
}
2931

    
2932
- (void)attemptToApplyCredentialsAndResume
2933
{
2934
	if ([self error] || [self isCancelled]) {
2935
		return;
2936
	}
2937
	
2938
	// Do we actually need to authenticate with a proxy?
2939
	if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) {
2940
		[self attemptToApplyProxyCredentialsAndResume];
2941
		return;
2942
	}
2943
	
2944
	// Read authentication data
2945
	if (!requestAuthentication) {
2946
		CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader);
2947
		requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
2948
		CFRelease(responseHeader);
2949
		[self setAuthenticationScheme:[NSMakeCollectable(CFHTTPAuthenticationCopyMethod(requestAuthentication)) autorelease]];
2950
	}
2951
	
2952
	if (!requestAuthentication) {
2953
		#if DEBUG_HTTP_AUTHENTICATION
2954
		ASI_DEBUG_LOG(@"[AUTH] Request %@ failed to read authentication information from response headers",self);
2955
		#endif
2956

    
2957
		[self cancelLoad];
2958
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
2959
		return;
2960
	}
2961
	
2962
	// Get the authentication realm
2963
	[self setAuthenticationRealm:nil];
2964
	if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
2965
		[self setAuthenticationRealm:[NSMakeCollectable(CFHTTPAuthenticationCopyRealm(requestAuthentication)) autorelease]];
2966
	}
2967
	
2968
	#if DEBUG_HTTP_AUTHENTICATION
2969
		NSString *realm = [self authenticationRealm];
2970
		if (realm) {
2971
			realm = [NSString stringWithFormat:@" (Realm: %@)",realm];
2972
		} else {
2973
			realm = @"";
2974
		}
2975
		if ([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM || [self authenticationRetryCount] == 0) {
2976
			ASI_DEBUG_LOG(@"[AUTH] Request %@ received 401 challenge and must authenticate using %@%@",self,[self authenticationScheme],realm);
2977
		} else {
2978
			ASI_DEBUG_LOG(@"[AUTH] Request %@ NTLM handshake step %i",self,[self authenticationRetryCount]+1);
2979
		}
2980
	#endif
2981

    
2982
	// See if authentication is valid
2983
	CFStreamError err;		
2984
	if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) {
2985
		
2986
		CFRelease(requestAuthentication);
2987
		requestAuthentication = NULL;
2988
		
2989
		// check for bad credentials, so we can give the delegate a chance to replace them
2990
		if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
2991

    
2992
			#if DEBUG_HTTP_AUTHENTICATION
2993
			ASI_DEBUG_LOG(@"[AUTH] Request %@ had bad credentials, will remove them from the session store if they are cached",self);
2994
			#endif
2995

    
2996
			// Prevent more than one request from asking for credentials at once
2997
			[delegateAuthenticationLock lock];
2998
			
2999
			// We know the credentials we just presented are bad, we should remove them from the session store too
3000
			[[self class] removeAuthenticationCredentialsFromSessionStore:requestCredentials];
3001
			[self setRequestCredentials:nil];
3002
			
3003
			// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
3004
			if ([self error] || [self isCancelled]) {
3005

    
3006
				#if DEBUG_HTTP_AUTHENTICATION
3007
				ASI_DEBUG_LOG(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self);
3008
				#endif
3009

    
3010
				[delegateAuthenticationLock unlock];
3011
				return;
3012
			}
3013

    
3014
			// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
3015
			if ([self useSessionPersistence]) {
3016
				NSDictionary *credentials = [self findSessionAuthenticationCredentials];
3017
				if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) {
3018

    
3019
					#if DEBUG_HTTP_AUTHENTICATION
3020
					ASI_DEBUG_LOG(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]);
3021
					#endif
3022

    
3023
					[delegateAuthenticationLock unlock];
3024
					[self startRequest];
3025
					return;
3026
				}
3027
			}
3028
			
3029
			[self setLastActivityTime:nil];
3030
			
3031
			if ([self willAskDelegateForCredentials]) {
3032

    
3033
				#if DEBUG_HTTP_AUTHENTICATION
3034
				ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask its delegate for credentials to use",self);
3035
				#endif
3036

    
3037
				[delegateAuthenticationLock unlock];
3038
				return;
3039
			}
3040
			if ([self showAuthenticationDialog]) {
3041

    
3042
				#if DEBUG_HTTP_AUTHENTICATION
3043
				ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask ASIAuthenticationDialog for credentials",self);
3044
				#endif
3045

    
3046
				[delegateAuthenticationLock unlock];
3047
				return;
3048
			}
3049
			[delegateAuthenticationLock unlock];
3050
		}
3051

    
3052
		#if DEBUG_HTTP_AUTHENTICATION
3053
		ASI_DEBUG_LOG(@"[AUTH] Request %@ has no credentials to present and must give up",self);
3054
		#endif
3055

    
3056
		[self cancelLoad];
3057
		[self failWithError:ASIAuthenticationError];
3058
		return;
3059
	}
3060
	
3061
	[self cancelLoad];
3062
	
3063
	if (requestCredentials) {
3064
		
3065
		if ((([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self authenticationRetryCount] < 2) && [self applyCredentials:requestCredentials]) {
3066
			[self startRequest];
3067
			
3068
			// We've failed NTLM authentication twice, we should assume our credentials are wrong
3069
		} else if ([self authenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self authenticationRetryCount ] == 2) {
3070
			#if DEBUG_HTTP_AUTHENTICATION
3071
			ASI_DEBUG_LOG(@"[AUTH] Request %@ has failed NTLM authentication",self);
3072
			#endif
3073

    
3074
			[self failWithError:ASIAuthenticationError];
3075
			
3076
		} else {
3077

    
3078
			#if DEBUG_HTTP_AUTHENTICATION
3079
			ASI_DEBUG_LOG(@"[AUTH] Request %@ had credentials and they were not marked as bad, but we got a 401 all the same.",self);
3080
			#endif
3081

    
3082
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
3083
		}
3084
		
3085
		// Are a user name & password needed?
3086
	}  else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) {
3087
		
3088
		// Prevent more than one request from asking for credentials at once
3089
		[delegateAuthenticationLock lock];
3090
		
3091
		// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
3092
		if ([self error] || [self isCancelled]) {
3093

    
3094
			#if DEBUG_HTTP_AUTHENTICATION
3095
			ASI_DEBUG_LOG(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self);
3096
			#endif
3097

    
3098
			[delegateAuthenticationLock unlock];
3099
			return;
3100
		}
3101
		
3102
		// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
3103
		if ([self useSessionPersistence]) {
3104
			NSDictionary *credentials = [self findSessionAuthenticationCredentials];
3105
			if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) {
3106

    
3107
				#if DEBUG_HTTP_AUTHENTICATION
3108
				ASI_DEBUG_LOG(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]);
3109
				#endif
3110

    
3111
				[delegateAuthenticationLock unlock];
3112
				[self startRequest];
3113
				return;
3114
			}
3115
		}
3116
		
3117

    
3118
		NSMutableDictionary *newCredentials = [self findCredentials];
3119
		
3120
		//If we have some credentials to use let's apply them to the request and continue
3121
		if (newCredentials) {
3122
			
3123
			if ([self applyCredentials:newCredentials]) {
3124
				[delegateAuthenticationLock unlock];
3125
				[self startRequest];
3126
			} else {
3127
				#if DEBUG_HTTP_AUTHENTICATION
3128
				ASI_DEBUG_LOG(@"[AUTH] Request %@ failed to apply credentials",self);
3129
				#endif
3130
				[delegateAuthenticationLock unlock];
3131
				[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
3132
			}
3133
			return;
3134
		}
3135
		if ([self willAskDelegateForCredentials]) {
3136

    
3137
			#if DEBUG_HTTP_AUTHENTICATION
3138
			ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask its delegate for credentials to use",self);
3139
			#endif
3140

    
3141
			[delegateAuthenticationLock unlock];
3142
			return;
3143
		}
3144
		if ([self showAuthenticationDialog]) {
3145

    
3146
			#if DEBUG_HTTP_AUTHENTICATION
3147
			ASI_DEBUG_LOG(@"[AUTH] Request %@ will ask ASIAuthenticationDialog for credentials",self);
3148
			#endif
3149

    
3150
			[delegateAuthenticationLock unlock];
3151
			return;
3152
		}
3153

    
3154
		#if DEBUG_HTTP_AUTHENTICATION
3155
		ASI_DEBUG_LOG(@"[AUTH] Request %@ has no credentials to present and must give up",self);
3156
		#endif
3157
		[delegateAuthenticationLock unlock];
3158
		[self failWithError:ASIAuthenticationError];
3159
		return;
3160
	}
3161
	
3162
}
3163

    
3164
- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword
3165
{
3166
	[self addRequestHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@",[ASIHTTPRequest base64forData:[[NSString stringWithFormat:@"%@:%@",theUsername,thePassword] dataUsingEncoding:NSUTF8StringEncoding]]]];	
3167
	[self setAuthenticationScheme:(NSString *)kCFHTTPAuthenticationSchemeBasic];
3168

    
3169
}
3170

    
3171

    
3172
#pragma mark stream status handlers
3173

    
3174
- (void)handleNetworkEvent:(CFStreamEventType)type
3175
{	
3176
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
3177

    
3178
	[[self cancelledLock] lock];
3179
	
3180
	if ([self complete] || [self isCancelled]) {
3181
		[[self cancelledLock] unlock];
3182
		[pool drain];
3183
		return;
3184
	}
3185

    
3186
	CFRetain(self);
3187

    
3188
    // Dispatch the stream events.
3189
    switch (type) {
3190
        case kCFStreamEventHasBytesAvailable:
3191
            [self handleBytesAvailable];
3192
            break;
3193
            
3194
        case kCFStreamEventEndEncountered:
3195
            [self handleStreamComplete];
3196
            break;
3197
            
3198
        case kCFStreamEventErrorOccurred:
3199
            [self handleStreamError];
3200
            break;
3201
            
3202
        default:
3203
            break;
3204
    }
3205
	
3206
	[self performThrottling];
3207
	
3208
	[[self cancelledLock] unlock];
3209
	
3210
	if ([self downloadComplete] && [self needsRedirect]) {
3211
		if (![self willAskDelegateToConfirmRedirect]) {
3212
			[self performRedirect];
3213
		}
3214
	} else if ([self downloadComplete] && [self authenticationNeeded]) {
3215
		[self attemptToApplyCredentialsAndResume];
3216
	}
3217

    
3218
	CFRelease(self);
3219
	[pool drain];
3220
}
3221

    
3222
- (BOOL)willAskDelegateToConfirmRedirect
3223
{
3224
	// We must lock to ensure delegate / queue aren't changed while we check them
3225
	[[self cancelledLock] lock];
3226

    
3227
	// 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
3228
	// We will check again on the main thread later
3229
	BOOL needToAskDelegateAboutRedirect = (([self delegate] && [[self delegate] respondsToSelector:[self willRedirectSelector]]) || ([self queue] && [[self queue] respondsToSelector:@selector(request:willRedirectToURL:)]));
3230

    
3231
	[[self cancelledLock] unlock];
3232

    
3233
	// Either the delegate or the queue's delegate is interested in being told when we are about to redirect
3234
	if (needToAskDelegateAboutRedirect) {
3235
		NSURL *newURL = [[[self redirectURL] copy] autorelease];
3236
		[self setRedirectURL:nil];
3237
		[self performSelectorOnMainThread:@selector(requestWillRedirectToURL:) withObject:newURL waitUntilDone:[NSThread isMainThread]];
3238
		return true;
3239
	}
3240
	return false;
3241
}
3242

    
3243
- (void)handleBytesAvailable
3244
{
3245
	if (![self responseHeaders]) {
3246
		[self readResponseHeaders];
3247
	}
3248
	
3249
	// If we've cancelled the load part way through (for example, after deciding to use a cached version)
3250
	if ([self complete]) {
3251
		return;
3252
	}
3253
	
3254
	// In certain (presumably very rare) circumstances, handleBytesAvailable seems to be called when there isn't actually any data available
3255
	// We'll check that there is actually data available to prevent blocking on CFReadStreamRead()
3256
	// So far, I've only seen this in the stress tests, so it might never happen in real-world situations.
3257
	if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)[self readStream])) {
3258
		return;
3259
	}
3260

    
3261
	long long bufferSize = 16384;
3262
	if (contentLength > 262144) {
3263
		bufferSize = 262144;
3264
	} else if (contentLength > 65536) {
3265
		bufferSize = 65536;
3266
	}
3267
	
3268
	// Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active
3269
	// This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit
3270
	
3271
	if ([[self class] isBandwidthThrottled]) {
3272
		[bandwidthThrottlingLock lock];
3273
		if (maxBandwidthPerSecond > 0) {
3274
			long long maxiumumSize  = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
3275
			if (maxiumumSize < 0) {
3276
				// 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
3277
				bufferSize = 1;
3278
			} else if (maxiumumSize/4 < bufferSize) {
3279
				// We were going to fetch more data that we should be allowed, so we'll reduce the size of our read
3280
				bufferSize = maxiumumSize/4;
3281
			}
3282
		}
3283
		if (bufferSize < 1) {
3284
			bufferSize = 1;
3285
		}
3286
		[bandwidthThrottlingLock unlock];
3287
	}
3288
	
3289
	
3290
    UInt8 buffer[bufferSize];
3291
    NSInteger bytesRead = [[self readStream] read:buffer maxLength:sizeof(buffer)];
3292

    
3293
    // Less than zero is an error
3294
    if (bytesRead < 0) {
3295
        [self handleStreamError];
3296
		
3297
	// If zero bytes were read, wait for the EOF to come.
3298
    } else if (bytesRead) {
3299

    
3300
		// If we are inflating the response on the fly
3301
		NSData *inflatedData = nil;
3302
		if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3303
			if (![self dataDecompressor]) {
3304
				[self setDataDecompressor:[ASIDataDecompressor decompressor]];
3305
			}
3306
			NSError *err = nil;
3307
			inflatedData = [[self dataDecompressor] uncompressBytes:buffer length:bytesRead error:&err];
3308
			if (err) {
3309
				[self failWithError:err];
3310
				return;
3311
			}
3312
		}
3313
		
3314
		[self setTotalBytesRead:[self totalBytesRead]+bytesRead];
3315
		[self setLastActivityTime:[NSDate date]];
3316

    
3317
		// For bandwidth measurement / throttling
3318
		[ASIHTTPRequest incrementBandwidthUsedInLastSecond:bytesRead];
3319
		
3320
		// If we need to redirect, and have automatic redirect on, and might be resuming a download, let's do nothing with the content
3321
		if ([self needsRedirect] && [self shouldRedirect] && [self allowResumeForFileDownloads]) {
3322
			return;
3323
		}
3324
		
3325
		BOOL dataWillBeHandledExternally = NO;
3326
		if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) {
3327
			dataWillBeHandledExternally = YES;
3328
		}
3329
		#if NS_BLOCKS_AVAILABLE
3330
		if (dataReceivedBlock) {
3331
			dataWillBeHandledExternally = YES;
3332
		}
3333
		#endif
3334
		// Does the delegate want to handle the data manually?
3335
		if (dataWillBeHandledExternally) {
3336

    
3337
			NSData *data = nil;
3338
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3339
				data = inflatedData;
3340
			} else {
3341
				data = [NSData dataWithBytes:buffer length:bytesRead];
3342
			}
3343
			[self performSelectorOnMainThread:@selector(passOnReceivedData:) withObject:data waitUntilDone:[NSThread isMainThread]];
3344
			
3345
		// Are we downloading to a file?
3346
		} else if ([self downloadDestinationPath]) {
3347
			BOOL append = NO;
3348
			if (![self fileDownloadOutputStream]) {
3349
				if (![self temporaryFileDownloadPath]) {
3350
					[self setTemporaryFileDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
3351
				} else if ([self allowResumeForFileDownloads] && [[self requestHeaders] objectForKey:@"Range"]) {
3352
					if ([[self responseHeaders] objectForKey:@"Content-Range"]) {
3353
						append = YES;
3354
					} else {
3355
						[self incrementDownloadSizeBy:-[self partialDownloadSize]];
3356
						[self setPartialDownloadSize:0];
3357
					}
3358
				}
3359

    
3360
				[self setFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryFileDownloadPath] append:append] autorelease]];
3361
				[[self fileDownloadOutputStream] open];
3362

    
3363
			}
3364
			[[self fileDownloadOutputStream] write:buffer maxLength:bytesRead];
3365

    
3366
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3367
				
3368
				if (![self inflatedFileDownloadOutputStream]) {
3369
					if (![self temporaryUncompressedDataDownloadPath]) {
3370
						[self setTemporaryUncompressedDataDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
3371
					}
3372
					
3373
					[self setInflatedFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryUncompressedDataDownloadPath] append:append] autorelease]];
3374
					[[self inflatedFileDownloadOutputStream] open];
3375
				}
3376

    
3377
				[[self inflatedFileDownloadOutputStream] write:[inflatedData bytes] maxLength:[inflatedData length]];
3378
			}
3379

    
3380
			
3381
		//Otherwise, let's add the data to our in-memory store
3382
		} else {
3383
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3384
				[rawResponseData appendData:inflatedData];
3385
			} else {
3386
				[rawResponseData appendBytes:buffer length:bytesRead];
3387
			}
3388
		}
3389
    }
3390
}
3391

    
3392
- (void)handleStreamComplete
3393
{	
3394

    
3395
#if DEBUG_REQUEST_STATUS
3396
	ASI_DEBUG_LOG(@"[STATUS] Request %@ finished downloading data (%qu bytes)",self, [self totalBytesRead]);
3397
#endif
3398
	[self setStatusTimer:nil];
3399
	[self setDownloadComplete:YES];
3400
	
3401
	if (![self responseHeaders]) {
3402
		[self readResponseHeaders];
3403
	}
3404

    
3405
	[progressLock lock];	
3406
	// Find out how much data we've uploaded so far
3407
	[self setLastBytesSent:totalBytesSent];	
3408
	[self setTotalBytesSent:[[NSMakeCollectable(CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount)) autorelease] unsignedLongLongValue]];
3409
	[self setComplete:YES];
3410
	if (![self contentLength]) {
3411
		[self setContentLength:[self totalBytesRead]];
3412
	}
3413
	[self updateProgressIndicators];
3414

    
3415
	
3416
	[[self postBodyReadStream] close];
3417
	[self setPostBodyReadStream:nil];
3418
	
3419
	[self setDataDecompressor:nil];
3420

    
3421
	NSError *fileError = nil;
3422
	
3423
	// Delete up the request body temporary file, if it exists
3424
	if ([self didCreateTemporaryPostDataFile] && ![self authenticationNeeded]) {
3425
		[self removeTemporaryUploadFile];
3426
		[self removeTemporaryCompressedUploadFile];
3427
	}
3428
	
3429
	// Close the output stream as we're done writing to the file
3430
	if ([self temporaryFileDownloadPath]) {
3431
		
3432
		[[self fileDownloadOutputStream] close];
3433
		[self setFileDownloadOutputStream:nil];
3434

    
3435
		[[self inflatedFileDownloadOutputStream] close];
3436
		[self setInflatedFileDownloadOutputStream:nil];
3437

    
3438
		// If we are going to redirect and we are resuming, let's ignore this download
3439
		if ([self shouldRedirect] && [self needsRedirect] && [self allowResumeForFileDownloads]) {
3440
		
3441
		} else if ([self isResponseCompressed]) {
3442
			
3443
			// Decompress the file directly to the destination path
3444
			if ([self shouldWaitToInflateCompressedResponses]) {
3445
				[ASIDataDecompressor uncompressDataFromFile:[self temporaryFileDownloadPath] toFile:[self downloadDestinationPath] error:&fileError];
3446

    
3447
			// Response should already have been inflated, move the temporary file to the destination path
3448
			} else {
3449
				NSError *moveError = nil;
3450
				[[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryUncompressedDataDownloadPath] toPath:[self downloadDestinationPath] error:&moveError];
3451
				if (moveError) {
3452
					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]];
3453
				}
3454
				[self setTemporaryUncompressedDataDownloadPath:nil];
3455

    
3456
			}
3457
			[self removeTemporaryDownloadFile];
3458

    
3459
		} else {
3460
	
3461
			//Remove any file at the destination path
3462
			NSError *moveError = nil;
3463
			if (![[self class] removeFileAtPath:[self downloadDestinationPath] error:&moveError]) {
3464
				fileError = moveError;
3465

    
3466
			}
3467

    
3468
			//Move the temporary file to the destination path
3469
			if (!fileError) {
3470
				[[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryFileDownloadPath] toPath:[self downloadDestinationPath] error:&moveError];
3471
				if (moveError) {
3472
					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]];
3473
				}
3474
				[self setTemporaryFileDownloadPath:nil];
3475
			}
3476
			
3477
		}
3478
	}
3479
	
3480
	// Save to the cache
3481
	if ([self downloadCache] && ![self didUseCachedResponse]) {
3482
		[[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
3483
	}
3484
	
3485
	[progressLock unlock];
3486

    
3487
	
3488
	[connectionsLock lock];
3489
	if (![self connectionCanBeReused]) {
3490
		[self unscheduleReadStream];
3491
	}
3492
	#if DEBUG_PERSISTENT_CONNECTIONS
3493
	if ([self requestID]) {
3494
		ASI_DEBUG_LOG(@"[CONNECTION] Request #%@ finished using connection #%@",[self requestID], [[self connectionInfo] objectForKey:@"id"]);
3495
	}
3496
	#endif
3497
	[[self connectionInfo] removeObjectForKey:@"request"];
3498
	[[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"];
3499
	[connectionsLock unlock];
3500
	
3501
	if (![self authenticationNeeded]) {
3502
		[self destroyReadStream];
3503
	}
3504
	
3505

    
3506
	if (![self needsRedirect] && ![self authenticationNeeded] && ![self didUseCachedResponse]) {
3507
		
3508
		if (fileError) {
3509
			[self failWithError:fileError];
3510
		} else {
3511
			[self requestFinished];
3512
		}
3513

    
3514
		[self markAsFinished];
3515
		
3516
	// If request has asked delegate or ASIAuthenticationDialog for credentials
3517
	} else if ([self authenticationNeeded]) {
3518
        // Do nothing.
3519
	}
3520

    
3521
}
3522

    
3523
- (void)markAsFinished
3524
{
3525
	// Autoreleased requests may well be dealloced here otherwise
3526
	CFRetain(self);
3527

    
3528
	// dealloc won't be called when running with GC, so we'll clean these up now
3529
	if (request) {
3530
		CFRelease(request);
3531
		request = nil;
3532
	}
3533
	if (requestAuthentication) {
3534
		CFRelease(requestAuthentication);
3535
		requestAuthentication = nil;
3536
	}
3537
	if (proxyAuthentication) {
3538
		CFRelease(proxyAuthentication);
3539
		proxyAuthentication = nil;
3540
	}
3541

    
3542
    BOOL wasInProgress = inProgress;
3543
    BOOL wasFinished = finished;
3544

    
3545
    if (!wasFinished)
3546
        [self willChangeValueForKey:@"isFinished"];
3547
    if (wasInProgress)
3548
        [self willChangeValueForKey:@"isExecuting"];
3549

    
3550
	[self setInProgress:NO];
3551
    finished = YES;
3552

    
3553
    if (wasInProgress)
3554
        [self didChangeValueForKey:@"isExecuting"];
3555
    if (!wasFinished)
3556
        [self didChangeValueForKey:@"isFinished"];
3557

    
3558
	#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
3559
	if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
3560
		dispatch_async(dispatch_get_main_queue(), ^{
3561
			if (backgroundTask != UIBackgroundTaskInvalid) {
3562
				[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
3563
				backgroundTask = UIBackgroundTaskInvalid;
3564
			}
3565
		});
3566
	}
3567
	#endif
3568
	CFRelease(self);
3569
}
3570

    
3571
- (void)useDataFromCache
3572
{
3573
	NSDictionary *headers = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
3574
	NSString *dataPath = [[self downloadCache] pathToCachedResponseDataForURL:[self url]];
3575

    
3576
	ASIHTTPRequest *theRequest = self;
3577
	if ([self mainRequest]) {
3578
		theRequest = [self mainRequest];
3579
	}
3580

    
3581
	if (headers && dataPath) {
3582

    
3583
		[self setResponseStatusCode:[[headers objectForKey:@"X-ASIHTTPRequest-Response-Status-Code"] intValue]];
3584
		[self setDidUseCachedResponse:YES];
3585
		[theRequest setResponseHeaders:headers];
3586

    
3587
		if ([theRequest downloadDestinationPath]) {
3588
			[theRequest setDownloadDestinationPath:dataPath];
3589
		} else {
3590
			[theRequest setRawResponseData:[NSMutableData dataWithData:[[self downloadCache] cachedResponseDataForURL:[self url]]]];
3591
		}
3592
		[theRequest setContentLength:[[[self responseHeaders] objectForKey:@"Content-Length"] longLongValue]];
3593
		[theRequest setTotalBytesRead:[self contentLength]];
3594

    
3595
		[theRequest parseStringEncodingFromHeaders];
3596

    
3597
		[theRequest setResponseCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:[self url]]];
3598

    
3599
		// See if we need to redirect
3600
		if ([self willRedirect]) {
3601
			if (![self willAskDelegateToConfirmRedirect]) {
3602
				[self performRedirect];
3603
			}
3604
			return;
3605
		}
3606
	}
3607

    
3608
	[theRequest setComplete:YES];
3609
	[theRequest setDownloadComplete:YES];
3610

    
3611
	// If we're pulling data from the cache without contacting the server at all, we won't have set originalURL yet
3612
	if ([self redirectCount] == 0) {
3613
		[theRequest setOriginalURL:[theRequest url]];
3614
	}
3615

    
3616
	[theRequest updateProgressIndicators];
3617
	[theRequest requestFinished];
3618
	[theRequest markAsFinished];	
3619
	if ([self mainRequest]) {
3620
		[self markAsFinished];
3621
	}
3622
}
3623

    
3624
- (BOOL)retryUsingNewConnection
3625
{
3626
	if ([self retryCount] == 0) {
3627

    
3628
		[self setWillRetryRequest:YES];
3629
		[self cancelLoad];
3630
		[self setWillRetryRequest:NO];
3631

    
3632
		#if DEBUG_PERSISTENT_CONNECTIONS
3633
			ASI_DEBUG_LOG(@"[CONNECTION] Request attempted to use connection #%@, but it has been closed - will retry with a new connection", [[self connectionInfo] objectForKey:@"id"]);
3634
		#endif
3635
		[connectionsLock lock];
3636
		[[self connectionInfo] removeObjectForKey:@"request"];
3637
		[persistentConnectionsPool removeObject:[self connectionInfo]];
3638
		[self setConnectionInfo:nil];
3639
		[connectionsLock unlock];
3640
		[self setRetryCount:[self retryCount]+1];
3641
		[self startRequest];
3642
		return YES;
3643
	}
3644
	#if DEBUG_PERSISTENT_CONNECTIONS
3645
		ASI_DEBUG_LOG(@"[CONNECTION] 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"]);
3646
	#endif	
3647
	return NO;
3648
}
3649

    
3650
- (void)handleStreamError
3651

    
3652
{
3653
	NSError *underlyingError = [NSMakeCollectable(CFReadStreamCopyError((CFReadStreamRef)[self readStream])) autorelease];
3654

    
3655
	if (![self error]) { // We may already have handled this error
3656
		
3657
		// First, check for a 'socket not connected', 'broken pipe' or 'connection lost' error
3658
		// This may occur when we've attempted to reuse a connection that should have been closed
3659
		// If we get this, we need to retry the request
3660
		// We'll only do this once - if it happens again on retry, we'll give up
3661
		// -1005 = kCFURLErrorNetworkConnectionLost - this doesn't seem to be declared on Mac OS 10.5
3662
		if (([[underlyingError domain] isEqualToString:NSPOSIXErrorDomain] && ([underlyingError code] == ENOTCONN || [underlyingError code] == EPIPE)) 
3663
			|| ([[underlyingError domain] isEqualToString:(NSString *)kCFErrorDomainCFNetwork] && [underlyingError code] == -1005)) {
3664
			if ([self retryUsingNewConnection]) {
3665
				return;
3666
			}
3667
		}
3668
		
3669
		NSString *reason = @"A connection failure occurred";
3670
		
3671
		// We'll use a custom error message for SSL errors, but you should always check underlying error if you want more details
3672
		// For some reason SecureTransport.h doesn't seem to be available on iphone, so error codes hard-coded
3673
		// 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
3674
		if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) {
3675
			if ([underlyingError code] <= -9800 && [underlyingError code] >= -9818) {
3676
				reason = [NSString stringWithFormat:@"%@: SSL problem (Possible causes may include a bad/expired/self-signed certificate, clock set to wrong date)",reason];
3677
			}
3678
		}
3679
		[self cancelLoad];
3680
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]];
3681
	} else {
3682
		[self cancelLoad];
3683
	}
3684
	[self checkRequestStatus];
3685
}
3686

    
3687
#pragma mark managing the read stream
3688

    
3689
- (void)destroyReadStream
3690
{
3691
    if ([self readStream]) {
3692
		[self unscheduleReadStream];
3693
		if (![self connectionCanBeReused]) {
3694
			[[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3695
			[[self readStream] close];
3696
		}
3697
		[self setReadStream:nil];
3698
    }	
3699
}
3700

    
3701
- (void)scheduleReadStream
3702
{
3703
	if ([self readStream] && ![self readStreamIsScheduled]) {
3704

    
3705
		[connectionsLock lock];
3706
		runningRequestCount++;
3707
		if (shouldUpdateNetworkActivityIndicator) {
3708
			[[self class] showNetworkActivityIndicator];
3709
		}
3710
		[connectionsLock unlock];
3711

    
3712
		// Reset the timeout
3713
		[self setLastActivityTime:[NSDate date]];
3714
		CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
3715
		CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt);
3716
		[[self readStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3717
		[self setReadStreamIsScheduled:YES];
3718
	}
3719
}
3720

    
3721

    
3722
- (void)unscheduleReadStream
3723
{
3724
	if ([self readStream] && [self readStreamIsScheduled]) {
3725

    
3726
		[connectionsLock lock];
3727
		runningRequestCount--;
3728
		if (shouldUpdateNetworkActivityIndicator && runningRequestCount == 0) {
3729
			// This call will wait half a second before turning off the indicator
3730
			// This can prevent flicker when you have a single request finish and then immediately start another request
3731
			// We run this on the main thread because we have no guarantee this thread will have a runloop in 0.5 seconds time
3732
			// 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
3733
			[[self class] performSelectorOnMainThread:@selector(hideNetworkActivityIndicatorAfterDelay) withObject:nil waitUntilDone:[NSThread isMainThread]];
3734
		}
3735
		[connectionsLock unlock];
3736

    
3737
		CFReadStreamSetClient((CFReadStreamRef)[self readStream], kCFStreamEventNone, NULL, NULL);
3738
		[[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3739
		[self setReadStreamIsScheduled:NO];
3740
	}
3741
}
3742

    
3743
#pragma mark cleanup
3744

    
3745
- (BOOL)removeTemporaryDownloadFile
3746
{
3747
	NSError *err = nil;
3748
	if ([self temporaryFileDownloadPath]) {
3749
		if (![[self class] removeFileAtPath:[self temporaryFileDownloadPath] error:&err]) {
3750
			[self failWithError:err];
3751
		}
3752
		[self setTemporaryFileDownloadPath:nil];
3753
	}
3754
	return (!err);
3755
}
3756

    
3757
- (BOOL)removeTemporaryUncompressedDownloadFile
3758
{
3759
	NSError *err = nil;
3760
	if ([self temporaryUncompressedDataDownloadPath]) {
3761
		if (![[self class] removeFileAtPath:[self temporaryUncompressedDataDownloadPath] error:&err]) {
3762
			[self failWithError:err];
3763
		}
3764
		[self setTemporaryUncompressedDataDownloadPath:nil];
3765
	}
3766
	return (!err);
3767
}
3768

    
3769
- (BOOL)removeTemporaryUploadFile
3770
{
3771
	NSError *err = nil;
3772
	if ([self postBodyFilePath]) {
3773
		if (![[self class] removeFileAtPath:[self postBodyFilePath] error:&err]) {
3774
			[self failWithError:err];
3775
		}
3776
		[self setPostBodyFilePath:nil];
3777
	}
3778
	return (!err);
3779
}
3780

    
3781
- (BOOL)removeTemporaryCompressedUploadFile
3782
{
3783
	NSError *err = nil;
3784
	if ([self compressedPostBodyFilePath]) {
3785
		if (![[self class] removeFileAtPath:[self compressedPostBodyFilePath] error:&err]) {
3786
			[self failWithError:err];
3787
		}
3788
		[self setCompressedPostBodyFilePath:nil];
3789
	}
3790
	return (!err);
3791
}
3792

    
3793
+ (BOOL)removeFileAtPath:(NSString *)path error:(NSError **)err
3794
{
3795
	NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
3796

    
3797
	if ([fileManager fileExistsAtPath:path]) {
3798
		NSError *removeError = nil;
3799
		[fileManager removeItemAtPath:path error:&removeError];
3800
		if (removeError) {
3801
			if (err) {
3802
				*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'",path],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]];
3803
			}
3804
			return NO;
3805
		}
3806
	}
3807
	return YES;
3808
}
3809

    
3810
#pragma mark Proxies
3811

    
3812
- (BOOL)configureProxies
3813
{
3814
	// Have details of the proxy been set on this request
3815
	if (![self isPACFileRequest] && (![self proxyHost] && ![self proxyPort])) {
3816

    
3817
		// If not, we need to figure out what they'll be
3818
		NSArray *proxies = nil;
3819

    
3820
		// Have we been given a proxy auto config file?
3821
		if ([self PACurl]) {
3822

    
3823
			// If yes, we'll need to fetch the PAC file asynchronously, so we stop this request to wait until we have the proxy details.
3824
			[self fetchPACFile];
3825
			return NO;
3826

    
3827
			// Detect proxy settings and apply them
3828
		} else {
3829

    
3830
#if TARGET_OS_IPHONE
3831
			NSDictionary *proxySettings = [NSMakeCollectable(CFNetworkCopySystemProxySettings()) autorelease];
3832
#else
3833
			NSDictionary *proxySettings = [NSMakeCollectable(SCDynamicStoreCopyProxies(NULL)) autorelease];
3834
#endif
3835

    
3836
			proxies = [NSMakeCollectable(CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings)) autorelease];
3837

    
3838
			// 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
3839
			NSDictionary *settings = [proxies objectAtIndex:0];
3840
			if ([settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]) {
3841
				[self setPACurl:[settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]];
3842
				[self fetchPACFile];
3843
				return NO;
3844
			}
3845
		}
3846

    
3847
		if (!proxies) {
3848
			[self setReadStream:nil];
3849
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to obtain information on proxy servers needed for request",NSLocalizedDescriptionKey,nil]]];
3850
			return NO;
3851
		}
3852
		// I don't really understand why the dictionary returned by CFNetworkCopyProxiesForURL uses different key names from CFNetworkCopySystemProxySettings/SCDynamicStoreCopyProxies
3853
		// 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)
3854
		if ([proxies count] > 0) {
3855
			NSDictionary *settings = [proxies objectAtIndex:0];
3856
			[self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]];
3857
			[self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]];
3858
			[self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]];
3859
		}
3860
	}
3861
	return YES;
3862
}
3863

    
3864

    
3865

    
3866
// Attempts to download a PAC (Proxy Auto-Configuration) file
3867
// PAC files at file://, http:// and https:// addresses are supported
3868
- (void)fetchPACFile
3869
{
3870
	// For file:// urls, we'll use an async NSInputStream (ASIHTTPRequest does not support file:// urls)
3871
	if ([[self PACurl] isFileURL]) {
3872
		NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:[[self PACurl] path]] autorelease];
3873
		[self setPACFileReadStream:stream];
3874
		[stream setDelegate:(id)self];
3875
		[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3876
		[stream open];
3877
		// If it takes more than timeOutSeconds to read the PAC, we'll just give up and assume no proxies
3878
		// We won't bother to handle cases where the first part of the PAC is read within timeOutSeconds, but the whole thing takes longer
3879
		// Either our PAC file is in easy reach, or it's going to slow things down to the point that it's probably better requests fail
3880
		[self performSelector:@selector(timeOutPACRead) withObject:nil afterDelay:[self timeOutSeconds]];
3881
		return;
3882
	}
3883

    
3884
	NSString *scheme = [[[self PACurl] scheme] lowercaseString];
3885
	if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) {
3886
		// Don't know how to read data from this URL, we'll have to give up
3887
		// We'll simply assume no proxies, and start the request as normal
3888
		[self startRequest];
3889
		return;
3890
	}
3891

    
3892
	// Create an ASIHTTPRequest to fetch the PAC file
3893
	ASIHTTPRequest *PACRequest = [ASIHTTPRequest requestWithURL:[self PACurl]];
3894

    
3895
	// Will prevent this request attempting to configure proxy settings for itself
3896
	[PACRequest setIsPACFileRequest:YES];
3897

    
3898
	[PACRequest setTimeOutSeconds:[self timeOutSeconds]];
3899

    
3900
	// If we're a synchronous request, we'll download the PAC file synchronously
3901
	if ([self isSynchronous]) {
3902
		[PACRequest startSynchronous];
3903
		if (![PACRequest error] && [PACRequest responseString]) {
3904
			[self runPACScript:[PACRequest responseString]];
3905
		}
3906
		[self startRequest];
3907
		return;
3908
	}
3909

    
3910
	[self setPACFileRequest:PACRequest];
3911

    
3912
	// Force this request to run before others in the shared queue
3913
	[PACRequest setQueuePriority:NSOperationQueuePriorityHigh];
3914

    
3915
	// We'll treat failure to download the PAC file the same as success - if we were unable to fetch a PAC file, we proceed as if we have no proxy server and let this request fail itself if necessary
3916
	[PACRequest setDelegate:self];
3917
	[PACRequest setDidFinishSelector:@selector(finishedDownloadingPACFile:)];
3918
	[PACRequest setDidFailSelector:@selector(finishedDownloadingPACFile:)];
3919
	[PACRequest startAsynchronous];
3920

    
3921
	// Temporarily increase the number of operations in the shared queue to give our request a chance to run
3922
	[connectionsLock lock];
3923
	[sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]+1];
3924
	[connectionsLock unlock];
3925
}
3926

    
3927
// Called as we read the PAC file from a file:// url
3928
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
3929
{
3930
	if (![self PACFileReadStream]) {
3931
		return;
3932
	}
3933
	if (eventCode == NSStreamEventHasBytesAvailable) {
3934

    
3935
		if (![self PACFileData]) {
3936
			[self setPACFileData:[NSMutableData data]];
3937
		}
3938
		// If your PAC file is larger than 16KB, you're just being cruel.
3939
		uint8_t buf[16384];
3940
		NSInteger len = [(NSInputStream *)stream read:buf maxLength:16384];
3941
		if (len) {
3942
			[[self PACFileData] appendBytes:(const void *)buf length:len];
3943
		}
3944

    
3945
	} else if (eventCode == NSStreamEventErrorOccurred || eventCode == NSStreamEventEndEncountered) {
3946

    
3947
		[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutPACRead) object:nil];
3948

    
3949
		[stream close];
3950
		[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3951
		[self setPACFileReadStream:nil];
3952

    
3953
		if (eventCode == NSStreamEventEndEncountered) {
3954
			// It sounds as though we have no idea what encoding a PAC file will use
3955
			static NSStringEncoding encodingsToTry[2] = {NSUTF8StringEncoding,NSISOLatin1StringEncoding};
3956
			NSUInteger i;
3957
			for (i=0; i<2; i++) {
3958
				NSString *pacScript =  [[[NSString alloc] initWithBytes:[[self PACFileData] bytes] length:[[self PACFileData] length] encoding:encodingsToTry[i]] autorelease];
3959
				if (pacScript) {
3960
					[self runPACScript:pacScript];
3961
					break;
3962
				}
3963
			}
3964
		}
3965
		[self setPACFileData:nil];
3966
		[self startRequest];
3967
	}
3968
}
3969

    
3970
// Called if it takes longer than timeOutSeconds to read the whole PAC file (when reading from a file:// url)
3971
- (void)timeOutPACRead
3972
{
3973
	[self stream:[self PACFileReadStream] handleEvent:NSStreamEventErrorOccurred];
3974
}
3975

    
3976
// Runs the downloaded PAC script
3977
- (void)runPACScript:(NSString *)script
3978
{
3979
	if (script) {
3980
		// From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
3981
		// Work around <rdar://problem/5530166>.  This dummy call to 
3982
		// CFNetworkCopyProxiesForURL initialise some state within CFNetwork 
3983
		// that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
3984
		CFRelease(CFNetworkCopyProxiesForURL((CFURLRef)[self url], NULL));
3985

    
3986
		// Obtain the list of proxies by running the autoconfiguration script
3987
		CFErrorRef err = NULL;
3988
		NSArray *proxies = [NSMakeCollectable(CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)[self url], &err)) autorelease];
3989
		if (!err && [proxies count] > 0) {
3990
			NSDictionary *settings = [proxies objectAtIndex:0];
3991
			[self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]];
3992
			[self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]];
3993
			[self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]];
3994
		}
3995
	}
3996
}
3997

    
3998
// Called if we successfully downloaded a PAC file from a webserver
3999
- (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest
4000
{
4001
	if (![theRequest error] && [theRequest responseString]) {
4002
		[self runPACScript:[theRequest responseString]];
4003
	}
4004

    
4005
	// Set the shared queue's maxConcurrentOperationCount back to normal
4006
	[connectionsLock lock];
4007
	[sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]-1];
4008
	[connectionsLock unlock];
4009

    
4010
	// We no longer need our PAC file request
4011
	[self setPACFileRequest:nil];
4012

    
4013
	// Start the request
4014
	[self startRequest];
4015
}
4016

    
4017

    
4018
#pragma mark persistent connections
4019

    
4020
- (NSNumber *)connectionID
4021
{
4022
	return [[self connectionInfo] objectForKey:@"id"];
4023
}
4024

    
4025
+ (void)expirePersistentConnections
4026
{
4027
	[connectionsLock lock];
4028
	NSUInteger i;
4029
	for (i=0; i<[persistentConnectionsPool count]; i++) {
4030
		NSDictionary *existingConnection = [persistentConnectionsPool objectAtIndex:i];
4031
		if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"expires"] timeIntervalSinceNow] <= 0) {
4032
#if DEBUG_PERSISTENT_CONNECTIONS
4033
			ASI_DEBUG_LOG(@"[CONNECTION] Closing connection #%i because it has expired",[[existingConnection objectForKey:@"id"] intValue]);
4034
#endif
4035
			NSInputStream *stream = [existingConnection objectForKey:@"stream"];
4036
			if (stream) {
4037
				[stream close];
4038
			}
4039
			[persistentConnectionsPool removeObject:existingConnection];
4040
			i--;
4041
		}
4042
	}	
4043
	[connectionsLock unlock];
4044
}
4045

    
4046
#pragma mark NSCopying
4047
- (id)copyWithZone:(NSZone *)zone
4048
{
4049
	// Don't forget - this will return a retained copy!
4050
	ASIHTTPRequest *newRequest = [[[self class] alloc] initWithURL:[self url]];
4051
	[newRequest setDelegate:[self delegate]];
4052
	[newRequest setRequestMethod:[self requestMethod]];
4053
	[newRequest setPostBody:[self postBody]];
4054
	[newRequest setShouldStreamPostDataFromDisk:[self shouldStreamPostDataFromDisk]];
4055
	[newRequest setPostBodyFilePath:[self postBodyFilePath]];
4056
	[newRequest setRequestHeaders:[[[self requestHeaders] mutableCopyWithZone:zone] autorelease]];
4057
	[newRequest setRequestCookies:[[[self requestCookies] mutableCopyWithZone:zone] autorelease]];
4058
	[newRequest setUseCookiePersistence:[self useCookiePersistence]];
4059
	[newRequest setUseKeychainPersistence:[self useKeychainPersistence]];
4060
	[newRequest setUseSessionPersistence:[self useSessionPersistence]];
4061
	[newRequest setAllowCompressedResponse:[self allowCompressedResponse]];
4062
	[newRequest setDownloadDestinationPath:[self downloadDestinationPath]];
4063
	[newRequest setTemporaryFileDownloadPath:[self temporaryFileDownloadPath]];
4064
	[newRequest setUsername:[self username]];
4065
	[newRequest setPassword:[self password]];
4066
	[newRequest setDomain:[self domain]];
4067
	[newRequest setProxyUsername:[self proxyUsername]];
4068
	[newRequest setProxyPassword:[self proxyPassword]];
4069
	[newRequest setProxyDomain:[self proxyDomain]];
4070
	[newRequest setProxyHost:[self proxyHost]];
4071
	[newRequest setProxyPort:[self proxyPort]];
4072
	[newRequest setProxyType:[self proxyType]];
4073
	[newRequest setUploadProgressDelegate:[self uploadProgressDelegate]];
4074
	[newRequest setDownloadProgressDelegate:[self downloadProgressDelegate]];
4075
	[newRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]];
4076
	[newRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]];
4077
	[newRequest setPostLength:[self postLength]];
4078
	[newRequest setHaveBuiltPostBody:[self haveBuiltPostBody]];
4079
	[newRequest setDidStartSelector:[self didStartSelector]];
4080
	[newRequest setDidFinishSelector:[self didFinishSelector]];
4081
	[newRequest setDidFailSelector:[self didFailSelector]];
4082
	[newRequest setTimeOutSeconds:[self timeOutSeconds]];
4083
	[newRequest setShouldResetDownloadProgress:[self shouldResetDownloadProgress]];
4084
	[newRequest setShouldResetUploadProgress:[self shouldResetUploadProgress]];
4085
	[newRequest setShowAccurateProgress:[self showAccurateProgress]];
4086
	[newRequest setDefaultResponseEncoding:[self defaultResponseEncoding]];
4087
	[newRequest setAllowResumeForFileDownloads:[self allowResumeForFileDownloads]];
4088
	[newRequest setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]];
4089
	[newRequest setTag:[self tag]];
4090
	[newRequest setUseHTTPVersionOne:[self useHTTPVersionOne]];
4091
	[newRequest setShouldRedirect:[self shouldRedirect]];
4092
	[newRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
4093
    [newRequest setClientCertificateIdentity:clientCertificateIdentity];
4094
	[newRequest setClientCertificates:[[clientCertificates copy] autorelease]];
4095
	[newRequest setPACurl:[self PACurl]];
4096
	[newRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
4097
	[newRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]];
4098
	[newRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]];
4099
	[newRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]];
4100
	[newRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]];
4101
    [newRequest setAuthenticationScheme:[self authenticationScheme]];
4102
	return newRequest;
4103
}
4104

    
4105
#pragma mark default time out
4106

    
4107
+ (NSTimeInterval)defaultTimeOutSeconds
4108
{
4109
	return defaultTimeOutSeconds;
4110
}
4111

    
4112
+ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds
4113
{
4114
	defaultTimeOutSeconds = newTimeOutSeconds;
4115
}
4116

    
4117

    
4118
#pragma mark client certificate
4119

    
4120
- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity {
4121
    if(clientCertificateIdentity) {
4122
        CFRelease(clientCertificateIdentity);
4123
    }
4124
    
4125
    clientCertificateIdentity = anIdentity;
4126
    
4127
	if (clientCertificateIdentity) {
4128
		CFRetain(clientCertificateIdentity);
4129
	}
4130
}
4131

    
4132

    
4133
#pragma mark session credentials
4134

    
4135
+ (NSMutableArray *)sessionProxyCredentialsStore
4136
{
4137
	[sessionCredentialsLock lock];
4138
	if (!sessionProxyCredentialsStore) {
4139
		sessionProxyCredentialsStore = [[NSMutableArray alloc] init];
4140
	}
4141
	[sessionCredentialsLock unlock];
4142
	return sessionProxyCredentialsStore;
4143
}
4144

    
4145
+ (NSMutableArray *)sessionCredentialsStore
4146
{
4147
	[sessionCredentialsLock lock];
4148
	if (!sessionCredentialsStore) {
4149
		sessionCredentialsStore = [[NSMutableArray alloc] init];
4150
	}
4151
	[sessionCredentialsLock unlock];
4152
	return sessionCredentialsStore;
4153
}
4154

    
4155
+ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
4156
{
4157
	[sessionCredentialsLock lock];
4158
	[self removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
4159
	[[[self class] sessionProxyCredentialsStore] addObject:credentials];
4160
	[sessionCredentialsLock unlock];
4161
}
4162

    
4163
+ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
4164
{
4165
	[sessionCredentialsLock lock];
4166
	[self removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
4167
	[[[self class] sessionCredentialsStore] addObject:credentials];
4168
	[sessionCredentialsLock unlock];
4169
}
4170

    
4171
+ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
4172
{
4173
	[sessionCredentialsLock lock];
4174
	NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
4175
	NSUInteger i;
4176
	for (i=0; i<[sessionCredentialsList count]; i++) {
4177
		NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
4178
		if ([theCredentials objectForKey:@"Credentials"] == credentials) {
4179
			[sessionCredentialsList removeObjectAtIndex:i];
4180
			[sessionCredentialsLock unlock];
4181
			return;
4182
		}
4183
	}
4184
	[sessionCredentialsLock unlock];
4185
}
4186

    
4187
+ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
4188
{
4189
	[sessionCredentialsLock lock];
4190
	NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
4191
	NSUInteger i;
4192
	for (i=0; i<[sessionCredentialsList count]; i++) {
4193
		NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
4194
		if ([theCredentials objectForKey:@"Credentials"] == credentials) {
4195
			[sessionCredentialsList removeObjectAtIndex:i];
4196
			[sessionCredentialsLock unlock];
4197
			return;
4198
		}
4199
	}
4200
	[sessionCredentialsLock unlock];
4201
}
4202

    
4203
- (NSDictionary *)findSessionProxyAuthenticationCredentials
4204
{
4205
	[sessionCredentialsLock lock];
4206
	NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
4207
	for (NSDictionary *theCredentials in sessionCredentialsList) {
4208
		if ([[theCredentials objectForKey:@"Host"] isEqualToString:[self proxyHost]] && [[theCredentials objectForKey:@"Port"] intValue] == [self proxyPort]) {
4209
			[sessionCredentialsLock unlock];
4210
			return theCredentials;
4211
		}
4212
	}
4213
	[sessionCredentialsLock unlock];
4214
	return nil;
4215
}
4216

    
4217

    
4218
- (NSDictionary *)findSessionAuthenticationCredentials
4219
{
4220
	[sessionCredentialsLock lock];
4221
	NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
4222
	NSURL *requestURL = [self url];
4223

    
4224
	BOOL haveFoundExactMatch;
4225
	NSDictionary *closeMatch = nil;
4226

    
4227
	// Loop through all the cached credentials we have, looking for the best match for this request
4228
	for (NSDictionary *theCredentials in sessionCredentialsList) {
4229
		
4230
		haveFoundExactMatch = NO;
4231
		NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"];
4232

    
4233
		// Find an exact match (same url)
4234
		if ([cachedCredentialsURL isEqual:[self url]]) {
4235
			haveFoundExactMatch = YES;
4236

    
4237
		// This is not an exact match for the url, and we already have a close match we can use
4238
		} else if (closeMatch) {
4239
			continue;
4240

    
4241
		// Find a close match (same host, scheme and port)
4242
		} else if ([[cachedCredentialsURL host] isEqualToString:[requestURL host]] && ([cachedCredentialsURL port] == [requestURL port] || ([requestURL port] && [[cachedCredentialsURL port] isEqualToNumber:[requestURL port]])) && [[cachedCredentialsURL scheme] isEqualToString:[requestURL scheme]]) {
4243
		} else {
4244
			continue;
4245
		}
4246

    
4247
		// 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
4248
		if ([self authenticationRealm] && ([theCredentials objectForKey:@"AuthenticationRealm"] && ![[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
4249
			continue;
4250
		}
4251

    
4252
		// If we have a username and password set on the request, check that they are the same as the cached ones
4253
		if ([self username] && [self password]) {
4254
			NSDictionary *usernameAndPassword = [theCredentials objectForKey:@"Credentials"];
4255
			NSString *storedUsername = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername];
4256
			NSString *storedPassword = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword];
4257
			if (![storedUsername isEqualToString:[self username]] || ![storedPassword isEqualToString:[self password]]) {
4258
				continue;
4259
			}
4260
		}
4261

    
4262
		// If we have an exact match for the url, use those credentials
4263
		if (haveFoundExactMatch) {
4264
			[sessionCredentialsLock unlock];
4265
			return theCredentials;
4266
		}
4267

    
4268
		// We have no exact match, let's remember that we have a good match for this server, and we'll use it at the end if we don't find an exact match
4269
		closeMatch = theCredentials;
4270
	}
4271
	[sessionCredentialsLock unlock];
4272

    
4273
	// Return credentials that matched on host, port and scheme, or nil if we didn't find any
4274
	return closeMatch;
4275
}
4276

    
4277
#pragma mark keychain storage
4278

    
4279
+ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4280
{
4281
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4282
	[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
4283
}
4284

    
4285
+ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm
4286
{
4287
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4288
	[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
4289
}
4290

    
4291
+ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4292
{
4293
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4294
	return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4295
}
4296

    
4297
+ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4298
{
4299
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4300
	return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4301
}
4302

    
4303
+ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4304
{
4305
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4306
	NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4307
	if (credential) {
4308
		[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace];
4309
	}
4310
}
4311

    
4312
+ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm
4313
{
4314
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4315
	NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4316
	if (credential) {
4317
		[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace];
4318
	}
4319
}
4320

    
4321
+ (NSMutableArray *)sessionCookies
4322
{
4323
	[sessionCookiesLock lock];
4324
	if (!sessionCookies) {
4325
		[ASIHTTPRequest setSessionCookies:[NSMutableArray array]];
4326
	}
4327
	NSMutableArray *cookies = [[sessionCookies retain] autorelease];
4328
	[sessionCookiesLock unlock];
4329
	return cookies;
4330
}
4331

    
4332
+ (void)setSessionCookies:(NSMutableArray *)newSessionCookies
4333
{
4334
	[sessionCookiesLock lock];
4335
	// Remove existing cookies from the persistent store
4336
	for (NSHTTPCookie *cookie in sessionCookies) {
4337
		[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
4338
	}
4339
	[sessionCookies release];
4340
	sessionCookies = [newSessionCookies retain];
4341
	[sessionCookiesLock unlock];
4342
}
4343

    
4344
+ (void)addSessionCookie:(NSHTTPCookie *)newCookie
4345
{
4346
	[sessionCookiesLock lock];
4347
	NSHTTPCookie *cookie;
4348
	NSUInteger i;
4349
	NSUInteger max = [[ASIHTTPRequest sessionCookies] count];
4350
	for (i=0; i<max; i++) {
4351
		cookie = [[ASIHTTPRequest sessionCookies] objectAtIndex:i];
4352
		if ([[cookie domain] isEqualToString:[newCookie domain]] && [[cookie path] isEqualToString:[newCookie path]] && [[cookie name] isEqualToString:[newCookie name]]) {
4353
			[[ASIHTTPRequest sessionCookies] removeObjectAtIndex:i];
4354
			break;
4355
		}
4356
	}
4357
	[[ASIHTTPRequest sessionCookies] addObject:newCookie];
4358
	[sessionCookiesLock unlock];
4359
}
4360

    
4361
// Dump all session data (authentication and cookies)
4362
+ (void)clearSession
4363
{
4364
	[sessionCredentialsLock lock];
4365
	[[[self class] sessionCredentialsStore] removeAllObjects];
4366
	[sessionCredentialsLock unlock];
4367
	[[self class] setSessionCookies:nil];
4368
	[[[self class] defaultCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
4369
}
4370

    
4371
#pragma mark get user agent
4372

    
4373
+ (NSString *)defaultUserAgentString
4374
{
4375
	@synchronized (self) {
4376

    
4377
		if (!defaultUserAgent) {
4378

    
4379
			NSBundle *bundle = [NSBundle bundleForClass:[self class]];
4380

    
4381
			// Attempt to find a name for this application
4382
			NSString *appName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
4383
			if (!appName) {
4384
				appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"];
4385
			}
4386

    
4387
			NSData *latin1Data = [appName dataUsingEncoding:NSUTF8StringEncoding];
4388
			appName = [[[NSString alloc] initWithData:latin1Data encoding:NSISOLatin1StringEncoding] autorelease];
4389

    
4390
			// If we couldn't find one, we'll give up (and ASIHTTPRequest will use the standard CFNetwork user agent)
4391
			if (!appName) {
4392
				return nil;
4393
			}
4394

    
4395
			NSString *appVersion = nil;
4396
			NSString *marketingVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
4397
			NSString *developmentVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
4398
			if (marketingVersionNumber && developmentVersionNumber) {
4399
				if ([marketingVersionNumber isEqualToString:developmentVersionNumber]) {
4400
					appVersion = marketingVersionNumber;
4401
				} else {
4402
					appVersion = [NSString stringWithFormat:@"%@ rv:%@",marketingVersionNumber,developmentVersionNumber];
4403
				}
4404
			} else {
4405
				appVersion = (marketingVersionNumber ? marketingVersionNumber : developmentVersionNumber);
4406
			}
4407

    
4408
			NSString *deviceName;
4409
			NSString *OSName;
4410
			NSString *OSVersion;
4411
			NSString *locale = [[NSLocale currentLocale] localeIdentifier];
4412

    
4413
			#if TARGET_OS_IPHONE
4414
				UIDevice *device = [UIDevice currentDevice];
4415
				deviceName = [device model];
4416
				OSName = [device systemName];
4417
				OSVersion = [device systemVersion];
4418

    
4419
			#else
4420
				deviceName = @"Macintosh";
4421
				OSName = @"Mac OS X";
4422

    
4423
				// From http://www.cocoadev.com/index.pl?DeterminingOSVersion
4424
				// We won't bother to check for systems prior to 10.4, since ASIHTTPRequest only works on 10.5+
4425
				OSErr err;
4426
				SInt32 versionMajor, versionMinor, versionBugFix;
4427
				err = Gestalt(gestaltSystemVersionMajor, &versionMajor);
4428
				if (err != noErr) return nil;
4429
				err = Gestalt(gestaltSystemVersionMinor, &versionMinor);
4430
				if (err != noErr) return nil;
4431
				err = Gestalt(gestaltSystemVersionBugFix, &versionBugFix);
4432
				if (err != noErr) return nil;
4433
				OSVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix];
4434
			#endif
4435

    
4436
			// Takes the form "My Application 1.0 (Macintosh; Mac OS X 10.5.7; en_GB)"
4437
			[self setDefaultUserAgentString:[NSString stringWithFormat:@"%@ %@ (%@; %@ %@; %@)", appName, appVersion, deviceName, OSName, OSVersion, locale]];	
4438
		}
4439
		return [[defaultUserAgent retain] autorelease];
4440
	}
4441
	return nil;
4442
}
4443

    
4444
+ (void)setDefaultUserAgentString:(NSString *)agent
4445
{
4446
	@synchronized (self) {
4447
		if (defaultUserAgent == agent) {
4448
			return;
4449
		}
4450
		[defaultUserAgent release];
4451
		defaultUserAgent = [agent copy];
4452
	}
4453
}
4454

    
4455

    
4456
#pragma mark mime-type detection
4457

    
4458
+ (NSString *)mimeTypeForFileAtPath:(NSString *)path
4459
{
4460
	if (![[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:path]) {
4461
		return nil;
4462
	}
4463
	// Borrowed from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
4464
	CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[path pathExtension], NULL);
4465
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
4466
    CFRelease(UTI);
4467
	if (!MIMEType) {
4468
		return @"application/octet-stream";
4469
	}
4470
    return [NSMakeCollectable(MIMEType) autorelease];
4471
}
4472

    
4473
#pragma mark bandwidth measurement / throttling
4474

    
4475
- (void)performThrottling
4476
{
4477
	if (![self readStream]) {
4478
		return;
4479
	}
4480
	[ASIHTTPRequest measureBandwidthUsage];
4481
	if ([ASIHTTPRequest isBandwidthThrottled]) {
4482
		[bandwidthThrottlingLock lock];
4483
		// Handle throttling
4484
		if (throttleWakeUpTime) {
4485
			if ([throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] > 0) {
4486
				if ([self readStreamIsScheduled]) {
4487
					[self unscheduleReadStream];
4488
					#if DEBUG_THROTTLING
4489
					ASI_DEBUG_LOG(@"[THROTTLING] Sleeping request %@ until after %@",self,throttleWakeUpTime);
4490
					#endif
4491
				}
4492
			} else {
4493
				if (![self readStreamIsScheduled]) {
4494
					[self scheduleReadStream];
4495
					#if DEBUG_THROTTLING
4496
					ASI_DEBUG_LOG(@"[THROTTLING] Waking up request %@",self);
4497
					#endif
4498
				}
4499
			}
4500
		} 
4501
		[bandwidthThrottlingLock unlock];
4502
		
4503
	// Bandwidth throttling must have been turned off since we last looked, let's re-schedule the stream
4504
	} else if (![self readStreamIsScheduled]) {
4505
		[self scheduleReadStream];			
4506
	}
4507
}
4508

    
4509
+ (BOOL)isBandwidthThrottled
4510
{
4511
#if TARGET_OS_IPHONE
4512
	[bandwidthThrottlingLock lock];
4513

    
4514
	BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwidthForWWANOnly && (maxBandwidthPerSecond > 0));
4515
	[bandwidthThrottlingLock unlock];
4516
	return throttle;
4517
#else
4518
	[bandwidthThrottlingLock lock];
4519
	BOOL throttle = (maxBandwidthPerSecond > 0);
4520
	[bandwidthThrottlingLock unlock];
4521
	return throttle;
4522
#endif
4523
}
4524

    
4525
+ (unsigned long)maxBandwidthPerSecond
4526
{
4527
	[bandwidthThrottlingLock lock];
4528
	unsigned long amount = maxBandwidthPerSecond;
4529
	[bandwidthThrottlingLock unlock];
4530
	return amount;
4531
}
4532

    
4533
+ (void)setMaxBandwidthPerSecond:(unsigned long)bytes
4534
{
4535
	[bandwidthThrottlingLock lock];
4536
	maxBandwidthPerSecond = bytes;
4537
	[bandwidthThrottlingLock unlock];
4538
}
4539

    
4540
+ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes
4541
{
4542
	[bandwidthThrottlingLock lock];
4543
	bandwidthUsedInLastSecond += bytes;
4544
	[bandwidthThrottlingLock unlock];
4545
}
4546

    
4547
+ (void)recordBandwidthUsage
4548
{
4549
	if (bandwidthUsedInLastSecond == 0) {
4550
		[bandwidthUsageTracker removeAllObjects];
4551
	} else {
4552
		NSTimeInterval interval = [bandwidthMeasurementDate timeIntervalSinceNow];
4553
		while ((interval < 0 || [bandwidthUsageTracker count] > 5) && [bandwidthUsageTracker count] > 0) {
4554
			[bandwidthUsageTracker removeObjectAtIndex:0];
4555
			interval++;
4556
		}
4557
	}
4558
	#if DEBUG_THROTTLING
4559
	ASI_DEBUG_LOG(@"[THROTTLING] ===Used: %u bytes of bandwidth in last measurement period===",bandwidthUsedInLastSecond);
4560
	#endif
4561
	[bandwidthUsageTracker addObject:[NSNumber numberWithUnsignedLong:bandwidthUsedInLastSecond]];
4562
	[bandwidthMeasurementDate release];
4563
	bandwidthMeasurementDate = [[NSDate dateWithTimeIntervalSinceNow:1] retain];
4564
	bandwidthUsedInLastSecond = 0;
4565
	
4566
	NSUInteger measurements = [bandwidthUsageTracker count];
4567
	unsigned long totalBytes = 0;
4568
	for (NSNumber *bytes in bandwidthUsageTracker) {
4569
		totalBytes += [bytes unsignedLongValue];
4570
	}
4571
	averageBandwidthUsedPerSecond = totalBytes/measurements;		
4572
}
4573

    
4574
+ (unsigned long)averageBandwidthUsedPerSecond
4575
{
4576
	[bandwidthThrottlingLock lock];
4577
	unsigned long amount = 	averageBandwidthUsedPerSecond;
4578
	[bandwidthThrottlingLock unlock];
4579
	return amount;
4580
}
4581

    
4582
+ (void)measureBandwidthUsage
4583
{
4584
	// 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
4585
	[bandwidthThrottlingLock lock];
4586

    
4587
	if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
4588
		[ASIHTTPRequest recordBandwidthUsage];
4589
	}
4590
	
4591
	// Are we performing bandwidth throttling?
4592
	if (
4593
	#if TARGET_OS_IPHONE
4594
	isBandwidthThrottled || (!shouldThrottleBandwidthForWWANOnly && (maxBandwidthPerSecond))
4595
	#else
4596
	maxBandwidthPerSecond
4597
	#endif
4598
	) {
4599
		// How much data can we still send or receive this second?
4600
		long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond;
4601
			
4602
		// Have we used up our allowance?
4603
		if (bytesRemaining < 0) {
4604
			
4605
			// 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)
4606
			double extraSleepyTime = (-bytesRemaining/(maxBandwidthPerSecond*1.0));
4607
			[throttleWakeUpTime release];
4608
			throttleWakeUpTime = [[NSDate alloc] initWithTimeInterval:extraSleepyTime sinceDate:bandwidthMeasurementDate];
4609
		}
4610
	}
4611
	[bandwidthThrottlingLock unlock];
4612
}
4613
	
4614
+ (unsigned long)maxUploadReadLength
4615
{
4616
	[bandwidthThrottlingLock lock];
4617
	
4618
	// 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
4619
	long long toRead = maxBandwidthPerSecond/4;
4620
	if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) {
4621
		toRead = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
4622
		if (toRead < 0) {
4623
			toRead = 0;
4624
		}
4625
	}
4626
	
4627
	if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
4628
		[throttleWakeUpTime release];
4629
		throttleWakeUpTime = [bandwidthMeasurementDate retain];
4630
	}
4631
	[bandwidthThrottlingLock unlock];	
4632
	return (unsigned long)toRead;
4633
}
4634
	
4635

    
4636
#if TARGET_OS_IPHONE
4637
+ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle
4638
{
4639
	if (throttle) {
4640
		[ASIHTTPRequest throttleBandwidthForWWANUsingLimit:ASIWWANBandwidthThrottleAmount];
4641
	} else {
4642
		[ASIHTTPRequest unsubscribeFromNetworkReachabilityNotifications];
4643
		[ASIHTTPRequest setMaxBandwidthPerSecond:0];
4644
		[bandwidthThrottlingLock lock];
4645
		isBandwidthThrottled = NO;
4646
		shouldThrottleBandwidthForWWANOnly = NO;
4647
		[bandwidthThrottlingLock unlock];
4648
	}
4649
}
4650

    
4651
+ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit
4652
{	
4653
	[bandwidthThrottlingLock lock];
4654
	shouldThrottleBandwidthForWWANOnly = YES;
4655
	maxBandwidthPerSecond = limit;
4656
	[ASIHTTPRequest registerForNetworkReachabilityNotifications];	
4657
	[bandwidthThrottlingLock unlock];
4658
	[ASIHTTPRequest reachabilityChanged:nil];
4659
}
4660

    
4661
#pragma mark reachability
4662

    
4663
+ (void)registerForNetworkReachabilityNotifications
4664
{
4665
	[[Reachability reachabilityForInternetConnection] startNotifier];
4666
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
4667
}
4668

    
4669

    
4670
+ (void)unsubscribeFromNetworkReachabilityNotifications
4671
{
4672
	[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
4673
}
4674

    
4675
+ (BOOL)isNetworkReachableViaWWAN
4676
{
4677
	return ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == ReachableViaWWAN);	
4678
}
4679

    
4680
+ (void)reachabilityChanged:(NSNotification *)note
4681
{
4682
	[bandwidthThrottlingLock lock];
4683
	isBandwidthThrottled = [ASIHTTPRequest isNetworkReachableViaWWAN];
4684
	[bandwidthThrottlingLock unlock];
4685
}
4686
#endif
4687

    
4688
#pragma mark queue
4689

    
4690
// Returns the shared queue
4691
+ (NSOperationQueue *)sharedQueue
4692
{
4693
    return [[sharedQueue retain] autorelease];
4694
}
4695

    
4696
#pragma mark cache
4697

    
4698
+ (void)setDefaultCache:(id <ASICacheDelegate>)cache
4699
{
4700
	@synchronized (self) {
4701
		[cache retain];
4702
		[defaultCache release];
4703
		defaultCache = cache;
4704
	}
4705
}
4706

    
4707
+ (id <ASICacheDelegate>)defaultCache
4708
{
4709
    @synchronized(self) {
4710
        return [[defaultCache retain] autorelease];
4711
    }
4712
	return nil;
4713
}
4714

    
4715

    
4716
#pragma mark network activity
4717

    
4718
+ (BOOL)isNetworkInUse
4719
{
4720
	[connectionsLock lock];
4721
	BOOL inUse = (runningRequestCount > 0);
4722
	[connectionsLock unlock];
4723
	return inUse;
4724
}
4725

    
4726
+ (void)setShouldUpdateNetworkActivityIndicator:(BOOL)shouldUpdate
4727
{
4728
	[connectionsLock lock];
4729
	shouldUpdateNetworkActivityIndicator = shouldUpdate;
4730
	[connectionsLock unlock];
4731
}
4732

    
4733
+ (void)showNetworkActivityIndicator
4734
{
4735
#if TARGET_OS_IPHONE
4736
	[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
4737
#endif
4738
}
4739

    
4740
+ (void)hideNetworkActivityIndicator
4741
{
4742
#if TARGET_OS_IPHONE
4743
	[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];	
4744
#endif
4745
}
4746

    
4747

    
4748
/* Always called on main thread */
4749
+ (void)hideNetworkActivityIndicatorAfterDelay
4750
{
4751
	[self performSelector:@selector(hideNetworkActivityIndicatorIfNeeeded) withObject:nil afterDelay:0.5];
4752
}
4753

    
4754
+ (void)hideNetworkActivityIndicatorIfNeeeded
4755
{
4756
	[connectionsLock lock];
4757
	if (runningRequestCount == 0) {
4758
		[self hideNetworkActivityIndicator];
4759
	}
4760
	[connectionsLock unlock];
4761
}
4762

    
4763

    
4764
#pragma mark threading behaviour
4765

    
4766
// In the default implementation, all requests run in a single background thread
4767
// Advanced users only: Override this method in a subclass for a different threading behaviour
4768
// Eg: return [NSThread mainThread] to run all requests in the main thread
4769
// Alternatively, you can create a thread on demand, or manage a pool of threads
4770
// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun())
4771
// Requests will stop the runloop when they complete
4772
// If you have multiple requests sharing the thread or you want to re-use the thread, you'll need to restart the runloop
4773
+ (NSThread *)threadForRequest:(ASIHTTPRequest *)request
4774
{
4775
	if (networkThread == nil) {
4776
		@synchronized(self) {
4777
			if (networkThread == nil) {
4778
				networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil];
4779
				[networkThread start];
4780
			}
4781
		}
4782
	}
4783
	return networkThread;
4784
}
4785

    
4786
+ (void)runRequests
4787
{
4788
	// Should keep the runloop from exiting
4789
	CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4790
	CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
4791
	CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
4792

    
4793
    BOOL runAlways = YES; // Introduced to cheat Static Analyzer
4794
	while (runAlways) {
4795
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
4796
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
4797
		[pool drain];
4798
	}
4799

    
4800
	// Should never be called, but anyway
4801
	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
4802
	CFRelease(source);
4803
}
4804

    
4805
#pragma mark miscellany 
4806

    
4807
#if TARGET_OS_IPHONE
4808
+ (BOOL)isMultitaskingSupported
4809
{
4810
	BOOL multiTaskingSupported = NO;
4811
	if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
4812
		multiTaskingSupported = [(id)[UIDevice currentDevice] isMultitaskingSupported];
4813
	}
4814
	return multiTaskingSupported;
4815
}
4816
#endif
4817

    
4818
// From: http://www.cocoadev.com/index.pl?BaseSixtyFour
4819

    
4820
+ (NSString*)base64forData:(NSData*)theData {
4821
	
4822
	const uint8_t* input = (const uint8_t*)[theData bytes];
4823
	NSInteger length = [theData length];
4824
	
4825
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
4826
	
4827
    NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
4828
    uint8_t* output = (uint8_t*)data.mutableBytes;
4829
	
4830
	NSInteger i,i2;
4831
    for (i=0; i < length; i += 3) {
4832
        NSInteger value = 0;
4833
		for (i2=0; i2<3; i2++) {
4834
            value <<= 8;
4835
            if (i+i2 < length) {
4836
                value |= (0xFF & input[i+i2]);
4837
            }
4838
        }
4839
		
4840
        NSInteger theIndex = (i / 3) * 4;
4841
        output[theIndex + 0] =                    table[(value >> 18) & 0x3F];
4842
        output[theIndex + 1] =                    table[(value >> 12) & 0x3F];
4843
        output[theIndex + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';
4844
        output[theIndex + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';
4845
    }
4846
	
4847
    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
4848
}
4849

    
4850
+ (NSDate *)expiryDateForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
4851
{
4852
	NSDictionary *responseHeaders = [request responseHeaders];
4853
  
4854
	// If we weren't given a custom max-age, lets look for one in the response headers
4855
	if (!maxAge) {
4856
		NSString *cacheControl = [[responseHeaders objectForKey:@"Cache-Control"] lowercaseString];
4857
		if (cacheControl) {
4858
			NSScanner *scanner = [NSScanner scannerWithString:cacheControl];
4859
			[scanner scanUpToString:@"max-age" intoString:NULL];
4860
			if ([scanner scanString:@"max-age" intoString:NULL]) {
4861
				[scanner scanString:@"=" intoString:NULL];
4862
				[scanner scanDouble:&maxAge];
4863
			}
4864
		}
4865
	}
4866
  
4867
	// RFC 2612 says max-age must override any Expires header
4868
	if (maxAge) {
4869
		return [[NSDate date] addTimeInterval:maxAge];
4870
	} else {
4871
		NSString *expires = [responseHeaders objectForKey:@"Expires"];
4872
		if (expires) {
4873
			return [ASIHTTPRequest dateFromRFC1123String:expires];
4874
		}
4875
	}
4876
	return nil;
4877
}
4878

    
4879
// Based on hints from http://stackoverflow.com/questions/1850824/parsing-a-rfc-822-date-with-nsdateformatter
4880
+ (NSDate *)dateFromRFC1123String:(NSString *)string
4881
{
4882
	NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
4883
	[formatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
4884
	// Does the string include a week day?
4885
	NSString *day = @"";
4886
	if ([string rangeOfString:@","].location != NSNotFound) {
4887
		day = @"EEE, ";
4888
	}
4889
	// Does the string include seconds?
4890
	NSString *seconds = @"";
4891
	if ([[string componentsSeparatedByString:@":"] count] == 3) {
4892
		seconds = @":ss";
4893
	}
4894
	[formatter setDateFormat:[NSString stringWithFormat:@"%@dd MMM yyyy HH:mm%@ z",day,seconds]];
4895
	return [formatter dateFromString:string];
4896
}
4897

    
4898
+ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType
4899
{
4900
	if (!contentType) {
4901
		return;
4902
	}
4903
	NSScanner *charsetScanner = [NSScanner scannerWithString: contentType];
4904
	if (![charsetScanner scanUpToString:@";" intoString:mimeType] || [charsetScanner scanLocation] == [contentType length]) {
4905
		*mimeType = [contentType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
4906
		return;
4907
	}
4908
	*mimeType = [*mimeType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
4909
	NSString *charsetSeparator = @"charset=";
4910
	NSString *IANAEncoding = nil;
4911

    
4912
	if ([charsetScanner scanUpToString: charsetSeparator intoString: NULL] && [charsetScanner scanLocation] < [contentType length]) {
4913
		[charsetScanner setScanLocation: [charsetScanner scanLocation] + [charsetSeparator length]];
4914
		[charsetScanner scanUpToString: @";" intoString: &IANAEncoding];
4915
	}
4916

    
4917
	if (IANAEncoding) {
4918
		CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding);
4919
		if (cfEncoding != kCFStringEncodingInvalidId) {
4920
			*stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
4921
		}
4922
	}
4923
}
4924

    
4925
#pragma mark -
4926
#pragma mark blocks
4927
#if NS_BLOCKS_AVAILABLE
4928
- (void)setStartedBlock:(ASIBasicBlock)aStartedBlock
4929
{
4930
	[startedBlock release];
4931
	startedBlock = [aStartedBlock copy];
4932
}
4933

    
4934
- (void)setHeadersReceivedBlock:(ASIHeadersBlock)aReceivedBlock
4935
{
4936
	[headersReceivedBlock release];
4937
	headersReceivedBlock = [aReceivedBlock copy];
4938
}
4939

    
4940
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock
4941
{
4942
	[completionBlock release];
4943
	completionBlock = [aCompletionBlock copy];
4944
}
4945

    
4946
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock
4947
{
4948
	[failureBlock release];
4949
	failureBlock = [aFailedBlock copy];
4950
}
4951

    
4952
- (void)setBytesReceivedBlock:(ASIProgressBlock)aBytesReceivedBlock
4953
{
4954
	[bytesReceivedBlock release];
4955
	bytesReceivedBlock = [aBytesReceivedBlock copy];
4956
}
4957

    
4958
- (void)setBytesSentBlock:(ASIProgressBlock)aBytesSentBlock
4959
{
4960
	[bytesSentBlock release];
4961
	bytesSentBlock = [aBytesSentBlock copy];
4962
}
4963

    
4964
- (void)setDownloadSizeIncrementedBlock:(ASISizeBlock)aDownloadSizeIncrementedBlock{
4965
	[downloadSizeIncrementedBlock release];
4966
	downloadSizeIncrementedBlock = [aDownloadSizeIncrementedBlock copy];
4967
}
4968

    
4969
- (void)setUploadSizeIncrementedBlock:(ASISizeBlock)anUploadSizeIncrementedBlock
4970
{
4971
	[uploadSizeIncrementedBlock release];
4972
	uploadSizeIncrementedBlock = [anUploadSizeIncrementedBlock copy];
4973
}
4974

    
4975
- (void)setDataReceivedBlock:(ASIDataBlock)aReceivedBlock
4976
{
4977
	[dataReceivedBlock release];
4978
	dataReceivedBlock = [aReceivedBlock copy];
4979
}
4980

    
4981
- (void)setAuthenticationNeededBlock:(ASIBasicBlock)anAuthenticationBlock
4982
{
4983
	[authenticationNeededBlock release];
4984
	authenticationNeededBlock = [anAuthenticationBlock copy];
4985
}
4986
- (void)setProxyAuthenticationNeededBlock:(ASIBasicBlock)aProxyAuthenticationBlock
4987
{
4988
	[proxyAuthenticationNeededBlock release];
4989
	proxyAuthenticationNeededBlock = [aProxyAuthenticationBlock copy];
4990
}
4991
- (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock
4992
{
4993
	[requestRedirectedBlock release];
4994
	requestRedirectedBlock = [aRedirectBlock copy];
4995
}
4996
#endif
4997

    
4998
#pragma mark ===
4999

    
5000
@synthesize username;
5001
@synthesize password;
5002
@synthesize userAgentString;
5003
@synthesize domain;
5004
@synthesize proxyUsername;
5005
@synthesize proxyPassword;
5006
@synthesize proxyDomain;
5007
@synthesize url;
5008
@synthesize originalURL;
5009
@synthesize delegate;
5010
@synthesize queue;
5011
@synthesize uploadProgressDelegate;
5012
@synthesize downloadProgressDelegate;
5013
@synthesize useKeychainPersistence;
5014
@synthesize useSessionPersistence;
5015
@synthesize useCookiePersistence;
5016
@synthesize downloadDestinationPath;
5017
@synthesize temporaryFileDownloadPath;
5018
@synthesize temporaryUncompressedDataDownloadPath;
5019
@synthesize didStartSelector;
5020
@synthesize didReceiveResponseHeadersSelector;
5021
@synthesize willRedirectSelector;
5022
@synthesize didFinishSelector;
5023
@synthesize didFailSelector;
5024
@synthesize didReceiveDataSelector;
5025
@synthesize authenticationRealm;
5026
@synthesize proxyAuthenticationRealm;
5027
@synthesize error;
5028
@synthesize complete;
5029
@synthesize requestHeaders;
5030
@synthesize responseHeaders;
5031
@synthesize responseCookies;
5032
@synthesize requestCookies;
5033
@synthesize requestCredentials;
5034
@synthesize responseStatusCode;
5035
@synthesize rawResponseData;
5036
@synthesize lastActivityTime;
5037
@synthesize timeOutSeconds;
5038
@synthesize requestMethod;
5039
@synthesize postBody;
5040
@synthesize compressedPostBody;
5041
@synthesize contentLength;
5042
@synthesize partialDownloadSize;
5043
@synthesize postLength;
5044
@synthesize shouldResetDownloadProgress;
5045
@synthesize shouldResetUploadProgress;
5046
@synthesize mainRequest;
5047
@synthesize totalBytesRead;
5048
@synthesize totalBytesSent;
5049
@synthesize showAccurateProgress;
5050
@synthesize uploadBufferSize;
5051
@synthesize defaultResponseEncoding;
5052
@synthesize responseEncoding;
5053
@synthesize allowCompressedResponse;
5054
@synthesize allowResumeForFileDownloads;
5055
@synthesize userInfo;
5056
@synthesize tag;
5057
@synthesize postBodyFilePath;
5058
@synthesize compressedPostBodyFilePath;
5059
@synthesize postBodyWriteStream;
5060
@synthesize postBodyReadStream;
5061
@synthesize shouldStreamPostDataFromDisk;
5062
@synthesize didCreateTemporaryPostDataFile;
5063
@synthesize useHTTPVersionOne;
5064
@synthesize lastBytesRead;
5065
@synthesize lastBytesSent;
5066
@synthesize cancelledLock;
5067
@synthesize haveBuiltPostBody;
5068
@synthesize fileDownloadOutputStream;
5069
@synthesize inflatedFileDownloadOutputStream;
5070
@synthesize authenticationRetryCount;
5071
@synthesize proxyAuthenticationRetryCount;
5072
@synthesize updatedProgress;
5073
@synthesize shouldRedirect;
5074
@synthesize validatesSecureCertificate;
5075
@synthesize needsRedirect;
5076
@synthesize redirectCount;
5077
@synthesize shouldCompressRequestBody;
5078
@synthesize proxyCredentials;
5079
@synthesize proxyHost;
5080
@synthesize proxyPort;
5081
@synthesize proxyType;
5082
@synthesize PACurl;
5083
@synthesize authenticationScheme;
5084
@synthesize proxyAuthenticationScheme;
5085
@synthesize shouldPresentAuthenticationDialog;
5086
@synthesize shouldPresentProxyAuthenticationDialog;
5087
@synthesize authenticationNeeded;
5088
@synthesize responseStatusMessage;
5089
@synthesize shouldPresentCredentialsBeforeChallenge;
5090
@synthesize haveBuiltRequestHeaders;
5091
@synthesize inProgress;
5092
@synthesize numberOfTimesToRetryOnTimeout;
5093
@synthesize retryCount;
5094
@synthesize willRetryRequest;
5095
@synthesize shouldAttemptPersistentConnection;
5096
@synthesize persistentConnectionTimeoutSeconds;
5097
@synthesize connectionCanBeReused;
5098
@synthesize connectionInfo;
5099
@synthesize readStream;
5100
@synthesize readStreamIsScheduled;
5101
@synthesize shouldUseRFC2616RedirectBehaviour;
5102
@synthesize downloadComplete;
5103
@synthesize requestID;
5104
@synthesize runLoopMode;
5105
@synthesize statusTimer;
5106
@synthesize downloadCache;
5107
@synthesize cachePolicy;
5108
@synthesize cacheStoragePolicy;
5109
@synthesize didUseCachedResponse;
5110
@synthesize secondsToCache;
5111
@synthesize clientCertificates;
5112
@synthesize redirectURL;
5113
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
5114
@synthesize shouldContinueWhenAppEntersBackground;
5115
#endif
5116
@synthesize dataDecompressor;
5117
@synthesize shouldWaitToInflateCompressedResponses;
5118

    
5119
@synthesize isPACFileRequest;
5120
@synthesize PACFileRequest;
5121
@synthesize PACFileReadStream;
5122
@synthesize PACFileData;
5123

    
5124
@synthesize isSynchronous;
5125
@end