Statistics
| Branch: | Revision:

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

History | View | Annotate | Download (177.6 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-8 2011-06-05";
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 shouldThrottleBandwithForWWANOnly = 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
	[userAgent 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
	[[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
464
}
465
// Always called on main thread
466
+ (void)releaseBlocks:(NSArray *)blocks
467
{
468
	// Blocks will be released when this method exits
469
}
470
#endif
471

    
472

    
473
#pragma mark setup request
474

    
475
- (void)addRequestHeader:(NSString *)header value:(NSString *)value
476
{
477
	if (!requestHeaders) {
478
		[self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]];
479
	}
480
	[requestHeaders setObject:value forKey:header];
481
}
482

    
483
// This function will be called either just before a request starts, or when postLength is needed, whichever comes first
484
// postLength must be set by the time this function is complete
485
- (void)buildPostBody
486
{
487

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

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

    
548
}
549

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

    
569
- (void)appendPostData:(NSData *)data
570
{
571
	[self setupPostBody];
572
	if ([data length] == 0) {
573
		return;
574
	}
575
	if ([self shouldStreamPostDataFromDisk]) {
576
		[[self postBodyWriteStream] write:[data bytes] maxLength:[data length]];
577
	} else {
578
		[[self postBody] appendData:data];
579
	}
580
}
581

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

    
604
- (NSString *)requestMethod
605
{
606
	[[self cancelledLock] lock];
607
	NSString *m = requestMethod;
608
	[[self cancelledLock] unlock];
609
	return m;
610
}
611

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

    
625
- (NSURL *)url
626
{
627
	[[self cancelledLock] lock];
628
	NSURL *u = url;
629
	[[self cancelledLock] unlock];
630
	return u;
631
}
632

    
633

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

    
659
- (id)delegate
660
{
661
	[[self cancelledLock] lock];
662
	id d = delegate;
663
	[[self cancelledLock] unlock];
664
	return d;
665
}
666

    
667
- (void)setDelegate:(id)newDelegate
668
{
669
	[[self cancelledLock] lock];
670
	delegate = newDelegate;
671
	[[self cancelledLock] unlock];
672
}
673

    
674
- (id)queue
675
{
676
	[[self cancelledLock] lock];
677
	id q = queue;
678
	[[self cancelledLock] unlock];
679
	return q;
680
}
681

    
682

    
683
- (void)setQueue:(id)newQueue
684
{
685
	[[self cancelledLock] lock];
686
	if (newQueue != queue) {
687
		[queue release];
688
		queue = [newQueue retain];
689
	}
690
	[[self cancelledLock] unlock];
691
}
692

    
693
#pragma mark get information about this request
694

    
695
// cancel the request - this must be run on the same thread as the request is running on
696
- (void)cancelOnRequestThread
697
{
698
	#if DEBUG_REQUEST_STATUS
699
	NSLog(@"[STATUS] Request cancelled: %@",self);
700
	#endif
701
    
702
	[[self cancelledLock] lock];
703

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

    
721
- (void)cancel
722
{
723
    [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];    
724
}
725

    
726
- (void)clearDelegatesAndCancel
727
{
728
	[[self cancelledLock] lock];
729

    
730
	// Clear delegates
731
	[self setDelegate:nil];
732
	[self setQueue:nil];
733
	[self setDownloadProgressDelegate:nil];
734
	[self setUploadProgressDelegate:nil];
735

    
736
	#if NS_BLOCKS_AVAILABLE
737
	// Clear blocks
738
	[self releaseBlocksOnMainThread];
739
	#endif
740

    
741
	[[self cancelledLock] unlock];
742
	[self cancel];
743
}
744

    
745

    
746
- (BOOL)isCancelled
747
{
748
    BOOL result;
749
    
750
	[[self cancelledLock] lock];
751
    result = cancelled;
752
    [[self cancelledLock] unlock];
753
    
754
    return result;
755
}
756

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

    
768
- (BOOL)isResponseCompressed
769
{
770
	NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
771
	return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
772
}
773

    
774
- (NSData *)responseData
775
{	
776
	if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) {
777
		return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL];
778
	} else {
779
		return [self rawResponseData];
780
	}
781
	return nil;
782
}
783

    
784
#pragma mark running a request
785

    
786
- (void)startSynchronous
787
{
788
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
789
	NSLog(@"[STATUS] Starting synchronous request %@",self);
790
#endif
791
	[self setSynchronous:YES];
792
	[self setRunLoopMode:ASIHTTPRequestRunLoopMode];
793
	[self setInProgress:YES];
794

    
795
	if (![self isCancelled] && ![self complete]) {
796
		[self main];
797
		while (!complete) {
798
			[[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]];
799
		}
800
	}
801

    
802
	[self setInProgress:NO];
803
}
804

    
805
- (void)start
806
{
807
	[self setInProgress:YES];
808
	[self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
809
}
810

    
811
- (void)startAsynchronous
812
{
813
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
814
	NSLog(@"[STATUS] Starting asynchronous request %@",self);
815
#endif
816
	[sharedQueue addOperation:self];
817
}
818

    
819
#pragma mark concurrency
820

    
821
- (BOOL)isConcurrent
822
{
823
    return YES;
824
}
825

    
826
- (BOOL)isFinished 
827
{
828
	return finished;
829
}
830

    
831
- (BOOL)isExecuting {
832
	return [self inProgress];
833
}
834

    
835
#pragma mark request logic
836

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

    
861

    
862
		// A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed.
863
		if ([self error]) {
864
			[self setComplete:YES];
865
			[self markAsFinished];
866
			return;		
867
		}
868

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

    
892
		// Create a new HTTP request.
893
		request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
894
		if (!request) {
895
			[self failWithError:ASIUnableToCreateRequestError];
896
			return;
897
		}
898

    
899
		//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
900
		if ([self mainRequest]) {
901
			[[self mainRequest] buildRequestHeaders];
902
		}
903
		
904
		// 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)
905
		[self buildRequestHeaders];
906
		
907
		if ([self downloadCache]) {
908

    
909
			// If this request should use the default policy, set its policy to the download cache's default policy
910
			if (![self cachePolicy]) {
911
				[self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
912
			}
913

    
914
			// If have have cached data that is valid for this request, use that and stop
915
			if ([[self downloadCache] canUseCachedDataForRequest:self]) {
916
				[self useDataFromCache];
917
				return;
918
			}
919

    
920
			// 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
921
			if ([self cachePolicy] & (ASIAskServerIfModifiedWhenStaleCachePolicy|ASIAskServerIfModifiedCachePolicy)) {
922

    
923
				NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
924
				if (cachedHeaders) {
925
					NSString *etag = [cachedHeaders objectForKey:@"Etag"];
926
					if (etag) {
927
						[[self requestHeaders] setObject:etag forKey:@"If-None-Match"];
928
					}
929
					NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"];
930
					if (lastModified) {
931
						[[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"];
932
					}
933
				}
934
			}
935
		}
936

    
937
		[self applyAuthorizationHeader];
938
		
939
		
940
		NSString *header;
941
		for (header in [self requestHeaders]) {
942
			CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]);
943
		}
944

    
945
		// If we immediately have access to proxy settings, start the request
946
		// Otherwise, we'll start downloading the proxy PAC file, and call startRequest once that process is complete
947
		if ([self configureProxies]) {
948
			[self startRequest];
949
		}
950

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

    
955
	} @finally {
956
		[[self cancelledLock] unlock];
957
	}
958
}
959

    
960
- (void)applyAuthorizationHeader
961
{
962
	// Do we want to send credentials before we are asked for them?
963
	if (![self shouldPresentCredentialsBeforeChallenge]) {
964
		#if DEBUG_HTTP_AUTHENTICATION
965
		NSLog(@"[AUTH] Request %@ will not send credentials to the server until it asks for them",self);
966
		#endif
967
		return;
968
	}
969

    
970
	NSDictionary *credentials = nil;
971

    
972
	// Do we already have an auth header?
973
	if (![[self requestHeaders] objectForKey:@"Authorization"]) {
974

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

    
979
			#if DEBUG_HTTP_AUTHENTICATION
980
			NSLog(@"[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);	
981
			#endif
982

    
983
		} else {
984

    
985
			// See if we have any cached credentials we can use in the session store
986
			if ([self useSessionPersistence]) {
987
				credentials = [self findSessionAuthenticationCredentials];
988

    
989
				if (credentials) {
990

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

    
995
						// If we've already talked to this server and have valid credentials, let's apply them to the request
996
						if (CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
997
							[self setAuthenticationScheme:[credentials objectForKey:@"AuthenticationScheme"]];
998
							#if DEBUG_HTTP_AUTHENTICATION
999
							NSLog(@"[AUTH] Request %@ found cached credentials (%@), will reuse without waiting for an authentication challenge",self,[credentials objectForKey:@"AuthenticationScheme"]);
1000
							#endif
1001
						} else {
1002
							[[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
1003
							#if DEBUG_HTTP_AUTHENTICATION
1004
							NSLog(@"[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);
1005
							#endif
1006
						}
1007

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

    
1022
	// Apply proxy authentication credentials
1023
	if ([self useSessionPersistence]) {
1024
		credentials = [self findSessionProxyAuthenticationCredentials];
1025
		if (credentials) {
1026
			if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
1027
				[[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
1028
			}
1029
		}
1030
	}
1031
}
1032

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

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

    
1111
- (void)updatePartialDownloadSize
1112
{
1113
	NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
1114

    
1115
	if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) {
1116
		NSError *err = nil;
1117
		[self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
1118
		if (err) {
1119
			[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]]];
1120
			return;
1121
		}
1122
	}
1123
}
1124

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

    
1159
	NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
1160

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

    
1189
	if (![self readStream]) {
1190
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
1191
        return;
1192
    }
1193

    
1194

    
1195
    
1196
    
1197
    //
1198
    // Handle SSL certificate settings
1199
    //
1200

    
1201
    if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
1202

    
1203
        NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
1204

    
1205
        // Tell CFNetwork not to validate SSL certificates
1206
        if (![self validatesSecureCertificate]) {
1207
            [sslProperties setObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
1208
        }
1209

    
1210
        // Tell CFNetwork to use a client certificate
1211
        if (clientCertificateIdentity) {
1212

    
1213
			NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
1214

    
1215
			// The first object in the array is our SecIdentityRef
1216
			[certificates addObject:(id)clientCertificateIdentity];
1217

    
1218
			// If we've added any additional certificates, add them too
1219
			for (id cert in clientCertificates) {
1220
				[certificates addObject:cert];
1221
			}
1222
            [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
1223
        }
1224

    
1225
        CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
1226
    }
1227

    
1228
	//
1229
	// Handle proxy settings
1230
	//
1231

    
1232
 	if ([self proxyHost] && [self proxyPort]) {
1233
		NSString *hostKey;
1234
		NSString *portKey;
1235

    
1236
		if (![self proxyType]) {
1237
			[self setProxyType:(NSString *)kCFProxyTypeHTTP];
1238
		}
1239

    
1240
		if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
1241
			hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
1242
			portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
1243
		} else {
1244
			hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
1245
			portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
1246
			if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
1247
				hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
1248
				portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
1249
			}
1250
		}
1251
		NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil];
1252

    
1253
		if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
1254
			CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse);
1255
		} else {
1256
			CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse);
1257
		}
1258
	}
1259

    
1260

    
1261
	//
1262
	// Handle persistent connections
1263
	//
1264
	
1265
	[ASIHTTPRequest expirePersistentConnections];
1266

    
1267
	[connectionsLock lock];
1268
	
1269
	
1270
	if (![[self url] host] || ![[self url] scheme]) {
1271
		[self setConnectionInfo:nil];
1272
		[self setShouldAttemptPersistentConnection:NO];
1273
	}
1274
	
1275
	// 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
1276
	NSInputStream *oldStream = nil;
1277
	
1278
	// Use a persistent connection if possible
1279
	if ([self shouldAttemptPersistentConnection]) {
1280
		
1281

    
1282
		// If we are redirecting, we will re-use the current connection only if we are connecting to the same server
1283
		if ([self connectionInfo]) {
1284
			
1285
			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]) {
1286
				[self setConnectionInfo:nil];
1287

    
1288
			// Check if we should have expired this connection
1289
			} else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) {
1290
				#if DEBUG_PERSISTENT_CONNECTIONS
1291
				NSLog(@"[CONNECTION] Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]);
1292
				#endif
1293
				[persistentConnectionsPool removeObject:[self connectionInfo]];
1294
				[self setConnectionInfo:nil];
1295

    
1296
			} else if ([[self connectionInfo] objectForKey:@"request"] != nil) {
1297
                //Some other request reused this connection already - we'll have to create a new one
1298
				#if DEBUG_PERSISTENT_CONNECTIONS
1299
                NSLog(@"%@ - 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]);
1300
				#endif
1301
                [self setConnectionInfo:nil];
1302
            }
1303
		}
1304
		
1305
		
1306
		
1307
		if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
1308
			
1309
			// Look for a connection to the same server in the pool
1310
			for (NSMutableDictionary *existingConnection in persistentConnectionsPool) {
1311
				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]) {
1312
					[self setConnectionInfo:existingConnection];
1313
				}
1314
			}
1315
		}
1316
		
1317
		if ([[self connectionInfo] objectForKey:@"stream"]) {
1318
			oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain];
1319

    
1320
		}
1321
		
1322
		// 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
1323
		if (![self connectionInfo]) {
1324
			[self setConnectionInfo:[NSMutableDictionary dictionary]];
1325
			nextConnectionNumberToCreate++;
1326
			[[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
1327
			[[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
1328
			[[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
1329
			[[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
1330
			[persistentConnectionsPool addObject:[self connectionInfo]];
1331
		}
1332
		
1333
		// If we are retrying this request, it will already have a requestID
1334
		if (![self requestID]) {
1335
			nextRequestID++;
1336
			[self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]];
1337
		}
1338
		[[self connectionInfo] setObject:[self requestID] forKey:@"request"];		
1339
		[[self connectionInfo] setObject:[self readStream] forKey:@"stream"];
1340
		CFReadStreamSetProperty((CFReadStreamRef)[self readStream],  kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
1341
		
1342
		#if DEBUG_PERSISTENT_CONNECTIONS
1343
		NSLog(@"[CONNECTION] Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]);
1344
		#endif
1345
		
1346
		
1347
		// Tag the stream with an id that tells it which connection to use behind the scenes
1348
		// See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
1349
		
1350
		CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
1351
	
1352
	} else {
1353
		#if DEBUG_PERSISTENT_CONNECTIONS
1354
		NSLog(@"[CONNECTION] Request %@ will not use a persistent connection",self);
1355
		#endif
1356
	}
1357
	
1358
	[connectionsLock unlock];
1359

    
1360
	// Schedule the stream
1361
	if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) {
1362
		[self scheduleReadStream];
1363
	}
1364
	
1365
	BOOL streamSuccessfullyOpened = NO;
1366

    
1367

    
1368
   // Start the HTTP connection
1369
	CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
1370
    if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
1371
		if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
1372
			streamSuccessfullyOpened = YES;
1373
		}
1374
	}
1375
	
1376
	// Here, we'll close the stream that was previously using this connection, if there was one
1377
	// 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
1378
	// http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html
1379
	if (oldStream) {
1380
		[oldStream close];
1381
		[oldStream release];
1382
		oldStream = nil;
1383
	}
1384

    
1385
	if (!streamSuccessfullyOpened) {
1386
		[self setConnectionCanBeReused:NO];
1387
		[self destroyReadStream];
1388
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
1389
		return;	
1390
	}
1391
	
1392
	if (![self mainRequest]) {
1393
		if ([self shouldResetUploadProgress]) {
1394
			if ([self showAccurateProgress]) {
1395
				[self incrementUploadSizeBy:[self postLength]];
1396
			} else {
1397
				[self incrementUploadSizeBy:1];	 
1398
			}
1399
			[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1];
1400
		}
1401
		if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) {
1402
			[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1];
1403
		}
1404
	}	
1405
	
1406
	
1407
	// Record when the request started, so we can timeout if nothing happens
1408
	[self setLastActivityTime:[NSDate date]];
1409
	[self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]];
1410
	[[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]];
1411
}
1412

    
1413
- (void)setStatusTimer:(NSTimer *)timer
1414
{
1415
	CFRetain(self);
1416
	// We must invalidate the old timer here, not before we've created and scheduled a new timer
1417
	// This is because the timer may be the only thing retaining an asynchronous request
1418
	if (statusTimer && timer != statusTimer) {
1419
		[statusTimer invalidate];
1420
		[statusTimer release];
1421
	}
1422
	statusTimer = [timer retain];
1423
	CFRelease(self);
1424
}
1425

    
1426
// This gets fired every 1/4 of a second to update the progress and work out if we need to timeout
1427
- (void)updateStatus:(NSTimer*)timer
1428
{
1429
	[self checkRequestStatus];
1430
	if (![self inProgress]) {
1431
		[self setStatusTimer:nil];
1432
	}
1433
}
1434

    
1435
- (void)performRedirect
1436
{
1437
	[self setURL:[self redirectURL]];
1438
	[self setComplete:YES];
1439
	[self setNeedsRedirect:NO];
1440
	[self setRedirectCount:[self redirectCount]+1];
1441

    
1442
	if ([self redirectCount] > RedirectionLimit) {
1443
		// Some naughty / badly coded website is trying to force us into a redirection loop. This is not cool.
1444
		[self failWithError:ASITooMuchRedirectionError];
1445
		[self setComplete:YES];
1446
	} else {
1447
		// Go all the way back to the beginning and build the request again, so that we can apply any new cookies
1448
		[self main];
1449
	}
1450
}
1451

    
1452
// Called by delegate to resume loading with a new url after the delegate received request:willRedirectToURL:
1453
- (void)redirectToURL:(NSURL *)newURL
1454
{
1455
	[self setRedirectURL:newURL];
1456
	[self performSelector:@selector(performRedirect) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
1457
}
1458

    
1459
- (BOOL)shouldTimeOut
1460
{
1461
	NSTimeInterval secondsSinceLastActivity = [[NSDate date] timeIntervalSinceDate:lastActivityTime];
1462
	// See if we need to timeout
1463
	if ([self readStream] && [self readStreamIsScheduled] && [self lastActivityTime] && [self timeOutSeconds] > 0 && secondsSinceLastActivity > [self timeOutSeconds]) {
1464
		
1465
		// We have no body, or we've sent more than the upload buffer size,so we can safely time out here
1466
		if ([self postLength] == 0 || ([self uploadBufferSize] > 0 && [self totalBytesSent] > [self uploadBufferSize])) {
1467
			return YES;
1468
			
1469
		// ***Black magic warning***
1470
		// We have a body, but we've taken longer than timeOutSeconds to upload the first small chunk of data
1471
		// 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.
1472
		} else if (secondsSinceLastActivity > [self timeOutSeconds]*1.5) {
1473
			return YES;
1474
		}
1475
	}
1476
	return NO;
1477
}
1478

    
1479
- (void)checkRequestStatus
1480
{
1481
	// We won't let the request cancel while we're updating progress / checking for a timeout
1482
	[[self cancelledLock] lock];
1483
	// See if our NSOperationQueue told us to cancel
1484
	if ([self isCancelled] || [self complete]) {
1485
		[[self cancelledLock] unlock];
1486
		return;
1487
	}
1488
	
1489
	[self performThrottling];
1490
	
1491
	if ([self shouldTimeOut]) {			
1492
		// Do we need to auto-retry this request?
1493
		if ([self numberOfTimesToRetryOnTimeout] > [self retryCount]) {
1494

    
1495
			// If we are resuming a download, we may need to update the Range header to take account of data we've just downloaded
1496
			[self updatePartialDownloadSize];
1497
			if ([self partialDownloadSize]) {
1498
				CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)@"Range", (CFStringRef)[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]);
1499
			}
1500
			[self setRetryCount:[self retryCount]+1];
1501
			[self unscheduleReadStream];
1502
			[[self cancelledLock] unlock];
1503
			[self startRequest];
1504
			return;
1505
		}
1506
		[self failWithError:ASIRequestTimedOutError];
1507
		[self cancelLoad];
1508
		[self setComplete:YES];
1509
		[[self cancelledLock] unlock];
1510
		return;
1511
	}
1512

    
1513
	// readStream will be null if we aren't currently running (perhaps we're waiting for a delegate to supply credentials)
1514
	if ([self readStream]) {
1515
		
1516
		// If we have a post body
1517
		if ([self postLength]) {
1518
		
1519
			[self setLastBytesSent:totalBytesSent];	
1520
			
1521
			// Find out how much data we've uploaded so far
1522
			[self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]];
1523
			if (totalBytesSent > lastBytesSent) {
1524
				
1525
				// We've uploaded more data,  reset the timeout
1526
				[self setLastActivityTime:[NSDate date]];
1527
				[ASIHTTPRequest incrementBandwidthUsedInLastSecond:(unsigned long)(totalBytesSent-lastBytesSent)];		
1528
						
1529
				#if DEBUG_REQUEST_STATUS
1530
				if ([self totalBytesSent] == [self postLength]) {
1531
					NSLog(@"[STATUS] Request %@ finished uploading data",self);
1532
				}
1533
				#endif
1534
			}
1535
		}
1536
			
1537
		[self updateProgressIndicators];
1538

    
1539
	}
1540
	
1541
	[[self cancelledLock] unlock];
1542
}
1543

    
1544

    
1545
// Cancel loading and clean up. DO NOT USE THIS TO CANCEL REQUESTS - use [request cancel] instead
1546
- (void)cancelLoad
1547
{
1548
	// If we're in the middle of downloading a PAC file, let's stop that first
1549
	if (PACFileReadStream) {
1550
		[PACFileReadStream setDelegate:nil];
1551
		[PACFileReadStream close];
1552
		[self setPACFileReadStream:nil];
1553
		[self setPACFileData:nil];
1554
	} else if (PACFileRequest) {
1555
		[PACFileRequest setDelegate:nil];
1556
		[PACFileRequest cancel];
1557
		[self setPACFileRequest:nil];
1558
	}
1559

    
1560
    [self destroyReadStream];
1561
	
1562
	[[self postBodyReadStream] close];
1563
	[self setPostBodyReadStream:nil];
1564
	
1565
    if ([self rawResponseData]) {
1566
		if (![self complete]) {
1567
			[self setRawResponseData:nil];
1568
		}
1569
	// If we were downloading to a file
1570
	} else if ([self temporaryFileDownloadPath]) {
1571
		[[self fileDownloadOutputStream] close];
1572
		[self setFileDownloadOutputStream:nil];
1573
		
1574
		[[self inflatedFileDownloadOutputStream] close];
1575
		[self setInflatedFileDownloadOutputStream:nil];
1576
		
1577
		// If we haven't said we might want to resume, let's remove the temporary file too
1578
		if (![self complete]) {
1579
			if (![self allowResumeForFileDownloads]) {
1580
				[self removeTemporaryDownloadFile];
1581
			}
1582
			[self removeTemporaryUncompressedDownloadFile];
1583
		}
1584
	}
1585
	
1586
	// Clean up any temporary file used to store request body for streaming
1587
	if (![self authenticationNeeded] && ![self willRetryRequest] && [self didCreateTemporaryPostDataFile]) {
1588
		[self removeTemporaryUploadFile];
1589
		[self removeTemporaryCompressedUploadFile];
1590
		[self setDidCreateTemporaryPostDataFile:NO];
1591
	}
1592
}
1593

    
1594
#pragma mark HEAD request
1595

    
1596
// Used by ASINetworkQueue to create a HEAD request appropriate for this request with the same headers (though you can use it yourself)
1597
- (ASIHTTPRequest *)HEADRequest
1598
{
1599
	ASIHTTPRequest *headRequest = [[self class] requestWithURL:[self url]];
1600
	
1601
	// Copy the properties that make sense for a HEAD request
1602
	[headRequest setRequestHeaders:[[[self requestHeaders] mutableCopy] autorelease]];
1603
	[headRequest setRequestCookies:[[[self requestCookies] mutableCopy] autorelease]];
1604
	[headRequest setUseCookiePersistence:[self useCookiePersistence]];
1605
	[headRequest setUseKeychainPersistence:[self useKeychainPersistence]];
1606
	[headRequest setUseSessionPersistence:[self useSessionPersistence]];
1607
	[headRequest setAllowCompressedResponse:[self allowCompressedResponse]];
1608
	[headRequest setUsername:[self username]];
1609
	[headRequest setPassword:[self password]];
1610
	[headRequest setDomain:[self domain]];
1611
	[headRequest setProxyUsername:[self proxyUsername]];
1612
	[headRequest setProxyPassword:[self proxyPassword]];
1613
	[headRequest setProxyDomain:[self proxyDomain]];
1614
	[headRequest setProxyHost:[self proxyHost]];
1615
	[headRequest setProxyPort:[self proxyPort]];
1616
	[headRequest setProxyType:[self proxyType]];
1617
	[headRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]];
1618
	[headRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]];
1619
	[headRequest setTimeOutSeconds:[self timeOutSeconds]];
1620
	[headRequest setUseHTTPVersionOne:[self useHTTPVersionOne]];
1621
	[headRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
1622
    [headRequest setClientCertificateIdentity:clientCertificateIdentity];
1623
	[headRequest setClientCertificates:[[clientCertificates copy] autorelease]];
1624
	[headRequest setPACurl:[self PACurl]];
1625
	[headRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
1626
	[headRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]];
1627
	[headRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]];
1628
	[headRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]];
1629
	[headRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]];
1630
	
1631
	[headRequest setMainRequest:self];
1632
	[headRequest setRequestMethod:@"HEAD"];
1633
	return headRequest;
1634
}
1635

    
1636

    
1637
#pragma mark upload/download progress
1638

    
1639

    
1640
- (void)updateProgressIndicators
1641
{
1642
	//Only update progress if this isn't a HEAD request used to preset the content-length
1643
	if (![self mainRequest]) {
1644
		if ([self showAccurateProgress] || ([self complete] && ![self updatedProgress])) {
1645
			[self updateUploadProgress];
1646
			[self updateDownloadProgress];
1647
		}
1648
	}
1649
}
1650

    
1651
- (id)uploadProgressDelegate
1652
{
1653
	[[self cancelledLock] lock];
1654
	id d = [[uploadProgressDelegate retain] autorelease];
1655
	[[self cancelledLock] unlock];
1656
	return d;
1657
}
1658

    
1659
- (void)setUploadProgressDelegate:(id)newDelegate
1660
{
1661
	[[self cancelledLock] lock];
1662
	uploadProgressDelegate = newDelegate;
1663

    
1664
	#if !TARGET_OS_IPHONE
1665
	// If the uploadProgressDelegate is an NSProgressIndicator, we set its MaxValue to 1.0 so we can update it as if it were a UIProgressView
1666
	double max = 1.0;
1667
	[ASIHTTPRequest performSelector:@selector(setMaxValue:) onTarget:&uploadProgressDelegate withObject:nil amount:&max callerToRetain:nil];
1668
	#endif
1669
	[[self cancelledLock] unlock];
1670
}
1671

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

    
1680
- (void)setDownloadProgressDelegate:(id)newDelegate
1681
{
1682
	[[self cancelledLock] lock];
1683
	downloadProgressDelegate = newDelegate;
1684

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

    
1693

    
1694
- (void)updateDownloadProgress
1695
{
1696
	// We won't update download progress until we've examined the headers, since we might need to authenticate
1697
	if (![self responseHeaders] || [self needsRedirect] || !([self contentLength] || [self complete])) {
1698
		return;
1699
	}
1700
		
1701
	unsigned long long bytesReadSoFar = [self totalBytesRead]+[self partialDownloadSize];
1702
	unsigned long long value = 0;
1703
	
1704
	if ([self showAccurateProgress] && [self contentLength]) {
1705
		value = bytesReadSoFar-[self lastBytesRead];
1706
		if (value == 0) {
1707
			return;
1708
		}
1709
	} else {
1710
		value = 1;
1711
		[self setUpdatedProgress:YES];
1712
	}
1713
	if (!value) {
1714
		return;
1715
	}
1716

    
1717
	[ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self];
1718
	[ASIHTTPRequest performSelector:@selector(request:didReceiveBytes:) onTarget:&downloadProgressDelegate withObject:self amount:&value callerToRetain:self];
1719

    
1720
	[ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:[self totalBytesRead]+[self partialDownloadSize] ofTotal:[self contentLength]+[self partialDownloadSize]];
1721

    
1722
	#if NS_BLOCKS_AVAILABLE
1723
    if (bytesReceivedBlock) {
1724
		unsigned long long totalSize = [self contentLength] + [self partialDownloadSize];
1725
		[self performBlockOnMainThread:^{ if (bytesReceivedBlock) { bytesReceivedBlock(value, totalSize); }}];
1726
    }
1727
	#endif
1728
	[self setLastBytesRead:bytesReadSoFar];
1729
}
1730

    
1731
- (void)updateUploadProgress
1732
{
1733
	if ([self isCancelled] || [self totalBytesSent] == 0) {
1734
		return;
1735
	}
1736
	
1737
	// 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)
1738
	// If request body is less than the buffer size, totalBytesSent will be the total size of the request body
1739
	// We will remove this from any progress display, as kCFStreamPropertyHTTPRequestBytesWrittenCount does not tell us how much data has actually be written
1740
	if ([self uploadBufferSize] == 0 && [self totalBytesSent] != [self postLength]) {
1741
		[self setUploadBufferSize:[self totalBytesSent]];
1742
		[self incrementUploadSizeBy:-[self uploadBufferSize]];
1743
	}
1744
	
1745
	unsigned long long value = 0;
1746
	
1747
	if ([self showAccurateProgress]) {
1748
		if ([self totalBytesSent] == [self postLength] || [self lastBytesSent] > 0) {
1749
			value = [self totalBytesSent]-[self lastBytesSent];
1750
		} else {
1751
			return;
1752
		}
1753
	} else {
1754
		value = 1;
1755
		[self setUpdatedProgress:YES];
1756
	}
1757
	
1758
	if (!value) {
1759
		return;
1760
	}
1761
	
1762
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&value callerToRetain:self];
1763
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&value callerToRetain:self];
1764
	[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:[self totalBytesSent]-[self uploadBufferSize] ofTotal:[self postLength]-[self uploadBufferSize]];
1765

    
1766
	#if NS_BLOCKS_AVAILABLE
1767
    if(bytesSentBlock){
1768
		unsigned long long totalSize = [self postLength];
1769
		[self performBlockOnMainThread:^{ if (bytesSentBlock) { bytesSentBlock(value, totalSize); }}];
1770
	}
1771
	#endif
1772
}
1773

    
1774

    
1775
- (void)incrementDownloadSizeBy:(long long)length
1776
{
1777
	[ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self];
1778
	[ASIHTTPRequest performSelector:@selector(request:incrementDownloadSizeBy:) onTarget:&downloadProgressDelegate withObject:self amount:&length callerToRetain:self];
1779

    
1780
	#if NS_BLOCKS_AVAILABLE
1781
    if(downloadSizeIncrementedBlock){
1782
		[self performBlockOnMainThread:^{ if (downloadSizeIncrementedBlock) { downloadSizeIncrementedBlock(length); }}];
1783
    }
1784
	#endif
1785
}
1786

    
1787
- (void)incrementUploadSizeBy:(long long)length
1788
{
1789
	[ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&queue withObject:self amount:&length callerToRetain:self];
1790
	[ASIHTTPRequest performSelector:@selector(request:incrementUploadSizeBy:) onTarget:&uploadProgressDelegate withObject:self amount:&length callerToRetain:self];
1791

    
1792
	#if NS_BLOCKS_AVAILABLE
1793
    if(uploadSizeIncrementedBlock) {
1794
		[self performBlockOnMainThread:^{ if (uploadSizeIncrementedBlock) { uploadSizeIncrementedBlock(length); }}];
1795
    }
1796
	#endif
1797
}
1798

    
1799

    
1800
-(void)removeUploadProgressSoFar
1801
{
1802
	long long progressToRemove = -[self totalBytesSent];
1803
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&queue withObject:self amount:&progressToRemove callerToRetain:self];
1804
	[ASIHTTPRequest performSelector:@selector(request:didSendBytes:) onTarget:&uploadProgressDelegate withObject:self amount:&progressToRemove callerToRetain:self];
1805
	[ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:[self postLength]];
1806

    
1807
	#if NS_BLOCKS_AVAILABLE
1808
    if(bytesSentBlock){
1809
		unsigned long long totalSize = [self postLength];
1810
		[self performBlockOnMainThread:^{  if (bytesSentBlock) { bytesSentBlock(progressToRemove, totalSize); }}];
1811
	}
1812
	#endif
1813
}
1814

    
1815
#if NS_BLOCKS_AVAILABLE
1816
- (void)performBlockOnMainThread:(ASIBasicBlock)block
1817
{
1818
	[self performSelectorOnMainThread:@selector(callBlock:) withObject:[[block copy] autorelease] waitUntilDone:[NSThread isMainThread]];
1819
}
1820

    
1821
- (void)callBlock:(ASIBasicBlock)block
1822
{
1823
	block();
1824
}
1825
#endif
1826

    
1827

    
1828
+ (void)performSelector:(SEL)selector onTarget:(id *)target withObject:(id)object amount:(void *)amount callerToRetain:(id)callerToRetain
1829
{
1830
	if ([*target respondsToSelector:selector]) {
1831
		NSMethodSignature *signature = nil;
1832
		signature = [*target methodSignatureForSelector:selector];
1833
		NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
1834

    
1835
		[invocation setSelector:selector];
1836
		
1837
		int argumentNumber = 2;
1838
		
1839
		// If we got an object parameter, we pass a pointer to the object pointer
1840
		if (object) {
1841
			[invocation setArgument:&object atIndex:argumentNumber];
1842
			argumentNumber++;
1843
		}
1844
		
1845
		// 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
1846
		if (amount) {
1847
			[invocation setArgument:amount atIndex:argumentNumber];
1848
		}
1849

    
1850
        SEL callback = @selector(performInvocation:onTarget:releasingObject:);
1851
        NSMethodSignature *cbSignature = [ASIHTTPRequest methodSignatureForSelector:callback];
1852
        NSInvocation *cbInvocation = [NSInvocation invocationWithMethodSignature:cbSignature];
1853
        [cbInvocation setSelector:callback];
1854
        [cbInvocation setTarget:self];
1855
        [cbInvocation setArgument:&invocation atIndex:2];
1856
        [cbInvocation setArgument:&target atIndex:3];
1857
		if (callerToRetain) {
1858
			[cbInvocation setArgument:&callerToRetain atIndex:4];
1859
		}
1860

    
1861
		CFRetain(invocation);
1862

    
1863
		// Used to pass in a request that we must retain until after the call
1864
		// We're using CFRetain rather than [callerToRetain retain] so things to avoid earthquakes when using garbage collection
1865
		if (callerToRetain) {
1866
			CFRetain(callerToRetain);
1867
		}
1868
        [cbInvocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:[NSThread isMainThread]];
1869
    }
1870
}
1871

    
1872
+ (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease
1873
{
1874
    if (*target && [*target respondsToSelector:[invocation selector]]) {
1875
        [invocation invokeWithTarget:*target];
1876
    }
1877
	CFRelease(invocation);
1878
	if (objectToRelease) {
1879
		CFRelease(objectToRelease);
1880
	}
1881
}
1882
	
1883
	
1884
+ (void)updateProgressIndicator:(id *)indicator withProgress:(unsigned long long)progress ofTotal:(unsigned long long)total
1885
{
1886
	#if TARGET_OS_IPHONE
1887
		// Cocoa Touch: UIProgressView
1888
		SEL selector = @selector(setProgress:);
1889
		float progressAmount = (float)((progress*1.0)/(total*1.0));
1890
		
1891
	#else
1892
		// Cocoa: NSProgressIndicator
1893
		double progressAmount = progressAmount = (progress*1.0)/(total*1.0);
1894
		SEL selector = @selector(setDoubleValue:);
1895
	#endif
1896
	
1897
	if (![*indicator respondsToSelector:selector]) {
1898
		return;
1899
	}
1900
	
1901
	[progressLock lock];
1902
	[ASIHTTPRequest performSelector:selector onTarget:indicator withObject:nil amount:&progressAmount callerToRetain:nil];
1903
	[progressLock unlock];
1904
}
1905

    
1906

    
1907
#pragma mark talking to delegates / calling blocks
1908

    
1909
/* ALWAYS CALLED ON MAIN THREAD! */
1910
- (void)requestStarted
1911
{
1912
	if ([self error] || [self mainRequest]) {
1913
		return;
1914
	}
1915
	if (delegate && [delegate respondsToSelector:didStartSelector]) {
1916
		[delegate performSelector:didStartSelector withObject:self];
1917
	}
1918
	#if NS_BLOCKS_AVAILABLE
1919
	if(startedBlock){
1920
		startedBlock();
1921
	}
1922
	#endif
1923
	if (queue && [queue respondsToSelector:@selector(requestStarted:)]) {
1924
		[queue performSelector:@selector(requestStarted:) withObject:self];
1925
	}
1926
}
1927

    
1928
/* ALWAYS CALLED ON MAIN THREAD! */
1929
- (void)requestRedirected
1930
{
1931
	if ([self error] || [self mainRequest]) {
1932
		return;
1933
	}
1934

    
1935
	if([[self delegate] respondsToSelector:@selector(requestRedirected:)]){
1936
		[[self delegate] performSelector:@selector(requestRedirected:) withObject:self];
1937
	}
1938

    
1939
	#if NS_BLOCKS_AVAILABLE
1940
	if(requestRedirectedBlock){
1941
		requestRedirectedBlock();
1942
	}
1943
	#endif
1944
}
1945

    
1946

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

    
1954
	if (delegate && [delegate respondsToSelector:didReceiveResponseHeadersSelector]) {
1955
		[delegate performSelector:didReceiveResponseHeadersSelector withObject:self withObject:newResponseHeaders];
1956
	}
1957

    
1958
	#if NS_BLOCKS_AVAILABLE
1959
	if(headersReceivedBlock){
1960
		headersReceivedBlock(newResponseHeaders);
1961
    }
1962
	#endif
1963

    
1964
	if (queue && [queue respondsToSelector:@selector(request:didReceiveResponseHeaders:)]) {
1965
		[queue performSelector:@selector(request:didReceiveResponseHeaders:) withObject:self withObject:newResponseHeaders];
1966
	}
1967
}
1968

    
1969
/* ALWAYS CALLED ON MAIN THREAD! */
1970
- (void)requestWillRedirectToURL:(NSURL *)newURL
1971
{
1972
	if ([self error] || [self mainRequest]) {
1973
		return;
1974
	}
1975
	if (delegate && [delegate respondsToSelector:willRedirectSelector]) {
1976
		[delegate performSelector:willRedirectSelector withObject:self withObject:newURL];
1977
	}
1978
	if (queue && [queue respondsToSelector:@selector(request:willRedirectToURL:)]) {
1979
		[queue performSelector:@selector(request:willRedirectToURL:) withObject:self withObject:newURL];
1980
	}
1981
}
1982

    
1983
// Subclasses might override this method to process the result in the same thread
1984
// If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done
1985
- (void)requestFinished
1986
{
1987
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
1988
	NSLog(@"[STATUS] Request finished: %@",self);
1989
#endif
1990
	if ([self error] || [self mainRequest]) {
1991
		return;
1992
	}
1993
	if ([self isPACFileRequest]) {
1994
		[self reportFinished];
1995
	} else {
1996
		[self performSelectorOnMainThread:@selector(reportFinished) withObject:nil waitUntilDone:[NSThread isMainThread]];
1997
	}
1998
}
1999

    
2000
/* ALWAYS CALLED ON MAIN THREAD! */
2001
- (void)reportFinished
2002
{
2003
	if (delegate && [delegate respondsToSelector:didFinishSelector]) {
2004
		[delegate performSelector:didFinishSelector withObject:self];
2005
	}
2006

    
2007
	#if NS_BLOCKS_AVAILABLE
2008
	if(completionBlock){
2009
		completionBlock();
2010
	}
2011
	#endif
2012

    
2013
	if (queue && [queue respondsToSelector:@selector(requestFinished:)]) {
2014
		[queue performSelector:@selector(requestFinished:) withObject:self];
2015
	}
2016
}
2017

    
2018
/* ALWAYS CALLED ON MAIN THREAD! */
2019
- (void)reportFailure
2020
{
2021
	if (delegate && [delegate respondsToSelector:didFailSelector]) {
2022
		[delegate performSelector:didFailSelector withObject:self];
2023
	}
2024

    
2025
	#if NS_BLOCKS_AVAILABLE
2026
    if(failureBlock){
2027
        failureBlock();
2028
    }
2029
	#endif
2030

    
2031
	if (queue && [queue respondsToSelector:@selector(requestFailed:)]) {
2032
		[queue performSelector:@selector(requestFailed:) withObject:self];
2033
	}
2034
}
2035

    
2036
/* ALWAYS CALLED ON MAIN THREAD! */
2037
- (void)passOnReceivedData:(NSData *)data
2038
{
2039
	if (delegate && [delegate respondsToSelector:didReceiveDataSelector]) {
2040
		[delegate performSelector:didReceiveDataSelector withObject:self withObject:data];
2041
	}
2042

    
2043
	#if NS_BLOCKS_AVAILABLE
2044
	if (dataReceivedBlock) {
2045
		dataReceivedBlock(data);
2046
	}
2047
	#endif
2048
}
2049

    
2050
// Subclasses might override this method to perform error handling in the same thread
2051
// If you do this, don't forget to call [super failWithError:] to let the queue / delegate know we're done
2052
- (void)failWithError:(NSError *)theError
2053
{
2054
#if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
2055
	NSLog(@"[STATUS] Request %@: %@",self,(theError == ASIRequestCancelledError ? @"Cancelled" : @"Failed"));
2056
#endif
2057
	[self setComplete:YES];
2058
	
2059
	// Invalidate the current connection so subsequent requests don't attempt to reuse it
2060
	if (theError && [theError code] != ASIAuthenticationErrorType && [theError code] != ASITooMuchRedirectionErrorType) {
2061
		[connectionsLock lock];
2062
		#if DEBUG_PERSISTENT_CONNECTIONS
2063
		NSLog(@"[CONNECTION] Request #%@ failed and will invalidate connection #%@",[self requestID],[[self connectionInfo] objectForKey:@"id"]);
2064
		#endif
2065
		[[self connectionInfo] removeObjectForKey:@"request"];
2066
		[persistentConnectionsPool removeObject:[self connectionInfo]];
2067
		[connectionsLock unlock];
2068
		[self destroyReadStream];
2069
	}
2070
	if ([self connectionCanBeReused]) {
2071
		[[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"];
2072
	}
2073
	
2074
    if ([self isCancelled] || [self error]) {
2075
		return;
2076
	}
2077
	
2078
	// If we have cached data, use it and ignore the error when using ASIFallbackToCacheIfLoadFailsCachePolicy
2079
	if ([self downloadCache] && ([self cachePolicy] & ASIFallbackToCacheIfLoadFailsCachePolicy)) {
2080
		if ([[self downloadCache] canUseCachedDataForRequest:self]) {
2081
			[self useDataFromCache];
2082
			return;
2083
		}
2084
	}
2085
	
2086
	
2087
	[self setError:theError];
2088
	
2089
	ASIHTTPRequest *failedRequest = self;
2090
	
2091
	// If this is a HEAD request created by an ASINetworkQueue or compatible queue delegate, make the main request fail
2092
	if ([self mainRequest]) {
2093
		failedRequest = [self mainRequest];
2094
		[failedRequest setError:theError];
2095
	}
2096

    
2097
	if ([self isPACFileRequest]) {
2098
		[failedRequest reportFailure];
2099
	} else {
2100
		[failedRequest performSelectorOnMainThread:@selector(reportFailure) withObject:nil waitUntilDone:[NSThread isMainThread]];
2101
	}
2102
	
2103
    if (!inProgress)
2104
    {
2105
        // if we're not in progress, we can't notify the queue we've finished (doing so can cause a crash later on)
2106
        // "markAsFinished" will be at the start of main() when we are started
2107
        return;
2108
    }
2109
	[self markAsFinished];
2110
}
2111

    
2112
#pragma mark parsing HTTP response headers
2113

    
2114
- (void)readResponseHeaders
2115
{
2116
	[self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
2117

    
2118
	CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPResponseHeader);
2119
	if (!message) {
2120
		return;
2121
	}
2122
	
2123
	// Make sure we've received all the headers
2124
	if (!CFHTTPMessageIsHeaderComplete(message)) {
2125
		CFRelease(message);
2126
		return;
2127
	}
2128

    
2129
	#if DEBUG_REQUEST_STATUS
2130
	if ([self totalBytesSent] == [self postLength]) {
2131
		NSLog(@"[STATUS] Request %@ received response headers",self);
2132
	}
2133
	#endif		
2134

    
2135
	[self setResponseHeaders:[(NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message) autorelease]];
2136
	[self setResponseStatusCode:(int)CFHTTPMessageGetResponseStatusCode(message)];
2137
	[self setResponseStatusMessage:[(NSString *)CFHTTPMessageCopyResponseStatusLine(message) autorelease]];
2138

    
2139
	if ([self downloadCache] && ([[self downloadCache] canUseCachedDataForRequest:self])) {
2140

    
2141
		// Update the expiry date
2142
		[[self downloadCache] updateExpiryForRequest:self maxAge:[self secondsToCache]];
2143

    
2144
		// Read the response from the cache
2145
		[self useDataFromCache];
2146

    
2147
		CFRelease(message);
2148
		return;
2149
	}
2150

    
2151
	// Is the server response a challenge for credentials?
2152
	if ([self responseStatusCode] == 401) {
2153
		[self setAuthenticationNeeded:ASIHTTPAuthenticationNeeded];
2154
	} else if ([self responseStatusCode] == 407) {
2155
		[self setAuthenticationNeeded:ASIProxyAuthenticationNeeded];
2156
	} else {
2157
		#if DEBUG_HTTP_AUTHENTICATION
2158
		if ([self authenticationScheme]) {
2159
			NSLog(@"[AUTH] Request %@ has passed %@ authentication",self,[self authenticationScheme]);
2160
		}
2161
		#endif
2162
	}
2163
		
2164
	// Authentication succeeded, or no authentication was required
2165
	if (![self authenticationNeeded]) {
2166

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

    
2170
			#if DEBUG_HTTP_AUTHENTICATION
2171
			NSLog(@"[AUTH] Request %@ passed BASIC authentication, and will save credentials in the session store for future use",self);
2172
			#endif
2173
			
2174
			NSMutableDictionary *newCredentials = [NSMutableDictionary dictionaryWithCapacity:2];
2175
			[newCredentials setObject:[self username] forKey:(NSString *)kCFHTTPAuthenticationUsername];
2176
			[newCredentials setObject:[self password] forKey:(NSString *)kCFHTTPAuthenticationPassword];
2177
			
2178
			// Store the credentials in the session 
2179
			NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary];
2180
			[sessionCredentials setObject:newCredentials forKey:@"Credentials"];
2181
			[sessionCredentials setObject:[self url] forKey:@"URL"];
2182
			[sessionCredentials setObject:(NSString *)kCFHTTPAuthenticationSchemeBasic forKey:@"AuthenticationScheme"];
2183
			[[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials];
2184
		}
2185
	}
2186

    
2187
	// Read response textEncoding
2188
	[self parseStringEncodingFromHeaders];
2189

    
2190
	// Handle cookies
2191
	NSArray *newCookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[self responseHeaders] forURL:[self url]];
2192
	[self setResponseCookies:newCookies];
2193
	
2194
	if ([self useCookiePersistence]) {
2195
		
2196
		// Store cookies in global persistent store
2197
		[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:newCookies forURL:[self url] mainDocumentURL:nil];
2198
		
2199
		// We also keep any cookies in the sessionCookies array, so that we have a reference to them if we need to remove them later
2200
		NSHTTPCookie *cookie;
2201
		for (cookie in newCookies) {
2202
			[ASIHTTPRequest addSessionCookie:cookie];
2203
		}
2204
	}
2205
	
2206
	// Do we need to redirect?
2207
	if (![self willRedirect]) {
2208
		// See if we got a Content-length header
2209
		NSString *cLength = [responseHeaders valueForKey:@"Content-Length"];
2210
		ASIHTTPRequest *theRequest = self;
2211
		if ([self mainRequest]) {
2212
			theRequest = [self mainRequest];
2213
		}
2214

    
2215
		if (cLength) {
2216
			unsigned long long length = strtoull([cLength UTF8String], NULL, 0);
2217

    
2218
			// Workaround for Apache HEAD requests for dynamically generated content returning the wrong Content-Length when using gzip
2219
			if ([self mainRequest] && [self allowCompressedResponse] && length == 20 && [self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2220
				[[self mainRequest] setShowAccurateProgress:NO];
2221
				[[self mainRequest] incrementDownloadSizeBy:1];
2222

    
2223
			} else {
2224
				[theRequest setContentLength:length];
2225
				if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2226
					[theRequest incrementDownloadSizeBy:[theRequest contentLength]+[theRequest partialDownloadSize]];
2227
				}
2228
			}
2229

    
2230
		} else if ([self showAccurateProgress] && [self shouldResetDownloadProgress]) {
2231
			[theRequest setShowAccurateProgress:NO];
2232
			[theRequest incrementDownloadSizeBy:1];
2233
		}
2234
	}
2235

    
2236
	// Handle connection persistence
2237
	if ([self shouldAttemptPersistentConnection]) {
2238
		
2239
		NSString *connectionHeader = [[[self responseHeaders] objectForKey:@"Connection"] lowercaseString];
2240

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

    
2246
			// See if server explicitly told us to close the connection
2247
			if (![connectionHeader isEqualToString:@"close"]) {
2248
				
2249
				NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"];
2250
				
2251
				// If we got a keep alive header, we'll reuse the connection for as long as the server tells us
2252
				if (keepAliveHeader) { 
2253
					int timeout = 0;
2254
					int max = 0;
2255
					NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader];
2256
					[scanner scanString:@"timeout=" intoString:NULL];
2257
					[scanner scanInt:&timeout];
2258
					[scanner scanUpToString:@"max=" intoString:NULL];
2259
					[scanner scanString:@"max=" intoString:NULL];
2260
					[scanner scanInt:&max];
2261
					if (max > 5) {
2262
						[self setConnectionCanBeReused:YES];
2263
						[self setPersistentConnectionTimeoutSeconds:timeout];
2264
						#if DEBUG_PERSISTENT_CONNECTIONS
2265
							NSLog(@"[CONNECTION] Got a keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]);
2266
						#endif					
2267
					}
2268
				
2269
				// Otherwise, we'll assume we can keep this connection open
2270
				} else {
2271
					[self setConnectionCanBeReused:YES];
2272
					#if DEBUG_PERSISTENT_CONNECTIONS
2273
						NSLog(@"[CONNECTION] Got no keep-alive header, will keep this connection open for %f seconds", [self persistentConnectionTimeoutSeconds]);
2274
					#endif
2275
				}
2276
			}
2277
		}
2278
	}
2279

    
2280
	CFRelease(message);
2281
	[self performSelectorOnMainThread:@selector(requestReceivedResponseHeaders:) withObject:[[[self responseHeaders] copy] autorelease] waitUntilDone:[NSThread isMainThread]];
2282
}
2283

    
2284
- (BOOL)willRedirect
2285
{
2286
	// Do we need to redirect?
2287
	if (![self shouldRedirect] || ![responseHeaders valueForKey:@"Location"]) {
2288
		return NO;
2289
	}
2290

    
2291
	// Note that ASIHTTPRequest does not currently support 305 Use Proxy
2292
	int responseCode = [self responseStatusCode];
2293
	if (responseCode != 301 && responseCode != 302 && responseCode != 303 && responseCode != 307) {
2294
		return NO;
2295
	}
2296

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

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

    
2304
	if (responseCode != 307 && (![self shouldUseRFC2616RedirectBehaviour] || responseCode == 303)) {
2305
		[self setRequestMethod:@"GET"];
2306
		[self setPostBody:nil];
2307
		[self setPostLength:0];
2308

    
2309
		// 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.
2310
		NSString *userAgentHeader = [[self requestHeaders] objectForKey:@"User-Agent"];
2311
		NSString *acceptHeader = [[self requestHeaders] objectForKey:@"Accept"];
2312
		[self setRequestHeaders:nil];
2313
		if (userAgentHeader) {
2314
			[self addRequestHeader:@"User-Agent" value:userAgentHeader];
2315
		}
2316
		if (acceptHeader) {
2317
			[self addRequestHeader:@"Accept" value:acceptHeader];
2318
		}
2319
		[self setHaveBuiltRequestHeaders:NO];
2320

    
2321
	} else {
2322
		// Force rebuild the cookie header incase we got some new cookies from this request
2323
		// All other request headers will remain as they are for 301 / 302 redirects
2324
		[self applyCookieHeader];
2325
	}
2326

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

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

    
2336
	#if DEBUG_REQUEST_STATUS
2337
	NSLog(@"[STATUS] Request will redirect (code: %i): %@",responseCode,self);
2338
	#endif
2339

    
2340
	return YES;
2341
}
2342

    
2343
- (void)parseStringEncodingFromHeaders
2344
{
2345
	// Handle response text encoding
2346
	NSStringEncoding charset = 0;
2347
	NSString *mimeType = nil;
2348
	[[self class] parseMimeType:&mimeType andResponseEncoding:&charset fromContentType:[[self responseHeaders] valueForKey:@"Content-Type"]];
2349
	if (charset != 0) {
2350
		[self setResponseEncoding:charset];
2351
	} else {
2352
		[self setResponseEncoding:[self defaultResponseEncoding]];
2353
	}
2354
}
2355

    
2356
#pragma mark http authentication
2357

    
2358
- (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials
2359
{
2360
	NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
2361
	if (authenticationCredentials) {
2362
		[ASIHTTPRequest saveCredentials:authenticationCredentials forProxy:[self proxyHost] port:[self proxyPort] realm:[self proxyAuthenticationRealm]];
2363
	}	
2364
}
2365

    
2366

    
2367
- (void)saveCredentialsToKeychain:(NSDictionary *)newCredentials
2368
{
2369
	NSURLCredential *authenticationCredentials = [NSURLCredential credentialWithUser:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationUsername] password:[newCredentials objectForKey:(NSString *)kCFHTTPAuthenticationPassword] persistence:NSURLCredentialPersistencePermanent];
2370
	
2371
	if (authenticationCredentials) {
2372
		[ASIHTTPRequest saveCredentials:authenticationCredentials forHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
2373
	}	
2374
}
2375

    
2376
- (BOOL)applyProxyCredentials:(NSDictionary *)newCredentials
2377
{
2378
	[self setProxyAuthenticationRetryCount:[self proxyAuthenticationRetryCount]+1];
2379
	
2380
	if (newCredentials && proxyAuthentication && request) {
2381

    
2382
		// Apply whatever credentials we've built up to the old request
2383
		if (CFHTTPMessageApplyCredentialDictionary(request, proxyAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
2384
			
2385
			//If we have credentials and they're ok, let's save them to the keychain
2386
			if (useKeychainPersistence) {
2387
				[self saveProxyCredentialsToKeychain:newCredentials];
2388
			}
2389
			if (useSessionPersistence) {
2390
				NSMutableDictionary *sessionProxyCredentials = [NSMutableDictionary dictionary];
2391
				[sessionProxyCredentials setObject:(id)proxyAuthentication forKey:@"Authentication"];
2392
				[sessionProxyCredentials setObject:newCredentials forKey:@"Credentials"];
2393
				[sessionProxyCredentials setObject:[self proxyHost] forKey:@"Host"];
2394
				[sessionProxyCredentials setObject:[NSNumber numberWithInt:[self proxyPort]] forKey:@"Port"];
2395
				[sessionProxyCredentials setObject:[self proxyAuthenticationScheme] forKey:@"AuthenticationScheme"];
2396
				[[self class] storeProxyAuthenticationCredentialsInSessionStore:sessionProxyCredentials];
2397
			}
2398
			[self setProxyCredentials:newCredentials];
2399
			return YES;
2400
		} else {
2401
			[[self class] removeProxyAuthenticationCredentialsFromSessionStore:newCredentials];
2402
		}
2403
	}
2404
	return NO;
2405
}
2406

    
2407
- (BOOL)applyCredentials:(NSDictionary *)newCredentials
2408
{
2409
	[self setAuthenticationRetryCount:[self authenticationRetryCount]+1];
2410
	
2411
	if (newCredentials && requestAuthentication && request) {
2412
		// Apply whatever credentials we've built up to the old request
2413
		if (CFHTTPMessageApplyCredentialDictionary(request, requestAuthentication, (CFMutableDictionaryRef)newCredentials, NULL)) {
2414
			
2415
			//If we have credentials and they're ok, let's save them to the keychain
2416
			if (useKeychainPersistence) {
2417
				[self saveCredentialsToKeychain:newCredentials];
2418
			}
2419
			if (useSessionPersistence) {
2420
				
2421
				NSMutableDictionary *sessionCredentials = [NSMutableDictionary dictionary];
2422
				[sessionCredentials setObject:(id)requestAuthentication forKey:@"Authentication"];
2423
				[sessionCredentials setObject:newCredentials forKey:@"Credentials"];
2424
				[sessionCredentials setObject:[self url] forKey:@"URL"];
2425
				[sessionCredentials setObject:[self authenticationScheme] forKey:@"AuthenticationScheme"];
2426
				if ([self authenticationRealm]) {
2427
					[sessionCredentials setObject:[self authenticationRealm] forKey:@"AuthenticationRealm"];
2428
				}
2429
				[[self class] storeAuthenticationCredentialsInSessionStore:sessionCredentials];
2430

    
2431
			}
2432
			[self setRequestCredentials:newCredentials];
2433
			return YES;
2434
		} else {
2435
			[[self class] removeAuthenticationCredentialsFromSessionStore:newCredentials];
2436
		}
2437
	}
2438
	return NO;
2439
}
2440

    
2441
- (NSMutableDictionary *)findProxyCredentials
2442
{
2443
	NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
2444
	
2445
	NSString *user = nil;
2446
	NSString *pass = nil;
2447
	
2448
	ASIHTTPRequest *theRequest = [self mainRequest];
2449
	// If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
2450
	if ([theRequest proxyUsername] && [theRequest proxyPassword]) {
2451
		user = [theRequest proxyUsername];
2452
		pass = [theRequest proxyPassword];
2453
		
2454
	// Let's try to use the ones set in this object
2455
	} else if ([self proxyUsername] && [self proxyPassword]) {
2456
		user = [self proxyUsername];
2457
		pass = [self proxyPassword];
2458
	}
2459

    
2460
	// When we connect to a website using NTLM via a proxy, we will use the main credentials
2461
	if ((!user || !pass) && [self proxyAuthenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM) {
2462
		user = [self username];
2463
		pass = [self password];
2464
	}
2465

    
2466

    
2467
	
2468
	// Ok, that didn't work, let's try the keychain
2469
	// For authenticating proxies, we'll look in the keychain regardless of the value of useKeychainPersistence
2470
	if ((!user || !pass)) {
2471
		NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForProxy:[self proxyHost] port:[self proxyPort] protocol:[[self url] scheme] realm:[self proxyAuthenticationRealm]];
2472
		if (authenticationCredentials) {
2473
			user = [authenticationCredentials user];
2474
			pass = [authenticationCredentials password];
2475
		}
2476
		
2477
	}
2478

    
2479
	// Handle NTLM, which requires a domain to be set too
2480
	if (CFHTTPAuthenticationRequiresAccountDomain(proxyAuthentication)) {
2481

    
2482
		NSString *ntlmDomain = [self proxyDomain];
2483

    
2484
		// If we have no domain yet
2485
		if (!ntlmDomain || [ntlmDomain length] == 0) {
2486

    
2487
			// Let's try to extract it from the username
2488
			NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"];
2489
			if ([ntlmComponents count] == 2) {
2490
				ntlmDomain = [ntlmComponents objectAtIndex:0];
2491
				user = [ntlmComponents objectAtIndex:1];
2492

    
2493
			// 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
2494
			} else {
2495
				ntlmDomain = [self domain];
2496
			}
2497
			if (!ntlmDomain) {
2498
				ntlmDomain = @"";
2499
			}
2500
		}
2501
		[newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
2502
	}
2503

    
2504

    
2505
	// If we have a username and password, let's apply them to the request and continue
2506
	if (user && pass) {
2507
		[newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
2508
		[newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
2509
		return newCredentials;
2510
	}
2511
	return nil;
2512
}
2513

    
2514

    
2515
- (NSMutableDictionary *)findCredentials
2516
{
2517
	NSMutableDictionary *newCredentials = [[[NSMutableDictionary alloc] init] autorelease];
2518

    
2519
	// First, let's look at the url to see if the username and password were included
2520
	NSString *user = [[self url] user];
2521
	NSString *pass = [[self url] password];
2522

    
2523
	if (user && pass) {
2524

    
2525
		#if DEBUG_HTTP_AUTHENTICATION
2526
		NSLog(@"[AUTH] Request %@ will use credentials set on its url",self);
2527
		#endif
2528

    
2529
	} else {
2530
		
2531
		// If this is a HEAD request generated by an ASINetworkQueue, we'll try to use the details from the main request
2532
		if ([self mainRequest] && [[self mainRequest] username] && [[self mainRequest] password]) {
2533
			user = [[self mainRequest] username];
2534
			pass = [[self mainRequest] password];
2535

    
2536
			#if DEBUG_HTTP_AUTHENTICATION
2537
			NSLog(@"[AUTH] Request %@ will use credentials from its parent request",self);
2538
			#endif
2539

    
2540
		// Let's try to use the ones set in this object
2541
		} else if ([self username] && [self password]) {
2542
			user = [self username];
2543
			pass = [self password];
2544

    
2545
			#if DEBUG_HTTP_AUTHENTICATION
2546
			NSLog(@"[AUTH] Request %@ will use username and password properties as credentials",self);
2547
			#endif
2548
		}		
2549
	}
2550
	
2551
	// Ok, that didn't work, let's try the keychain
2552
	if ((!user || !pass) && useKeychainPersistence) {
2553
		NSURLCredential *authenticationCredentials = [ASIHTTPRequest savedCredentialsForHost:[[self url] host] port:[[[self url] port] intValue] protocol:[[self url] scheme] realm:[self authenticationRealm]];
2554
		if (authenticationCredentials) {
2555
			user = [authenticationCredentials user];
2556
			pass = [authenticationCredentials password];
2557
			#if DEBUG_HTTP_AUTHENTICATION
2558
			if (user && pass) {
2559
				NSLog(@"[AUTH] Request %@ will use credentials from the keychain",self);
2560
			}
2561
			#endif
2562
		}
2563
	}
2564

    
2565
	// Handle NTLM, which requires a domain to be set too
2566
	if (CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
2567

    
2568
		NSString *ntlmDomain = [self domain];
2569

    
2570
		// If we have no domain yet, let's try to extract it from the username
2571
		if (!ntlmDomain || [ntlmDomain length] == 0) {
2572
			ntlmDomain = @"";
2573
			NSArray* ntlmComponents = [user componentsSeparatedByString:@"\\"];
2574
			if ([ntlmComponents count] == 2) {
2575
				ntlmDomain = [ntlmComponents objectAtIndex:0];
2576
				user = [ntlmComponents objectAtIndex:1];
2577
			}
2578
		}
2579
		[newCredentials setObject:ntlmDomain forKey:(NSString *)kCFHTTPAuthenticationAccountDomain];
2580
	}
2581

    
2582
	// If we have a username and password, let's apply them to the request and continue
2583
	if (user && pass) {
2584
		[newCredentials setObject:user forKey:(NSString *)kCFHTTPAuthenticationUsername];
2585
		[newCredentials setObject:pass forKey:(NSString *)kCFHTTPAuthenticationPassword];
2586
		return newCredentials;
2587
	}
2588
	return nil;
2589
}
2590

    
2591
// Called by delegate or authentication dialog to resume loading once authentication info has been populated
2592
- (void)retryUsingSuppliedCredentials
2593
{
2594
	#if DEBUG_HTTP_AUTHENTICATION
2595
		NSLog(@"[AUTH] Request %@ received credentials from its delegate or an ASIAuthenticationDialog, will retry",self);
2596
	#endif
2597
	//If the url was changed by the delegate, our CFHTTPMessageRef will be NULL and we'll go back to the start
2598
	if (!request) {
2599
		[self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2600
		return;
2601
	}
2602
	[self performSelector:@selector(attemptToApplyCredentialsAndResume) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2603
}
2604

    
2605
// Called by delegate or authentication dialog to cancel authentication
2606
- (void)cancelAuthentication
2607
{
2608
	#if DEBUG_HTTP_AUTHENTICATION
2609
		NSLog(@"[AUTH] Request %@ had authentication cancelled by its delegate or an ASIAuthenticationDialog",self);
2610
	#endif
2611
	[self performSelector:@selector(failAuthentication) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
2612
}
2613

    
2614
- (void)failAuthentication
2615
{
2616
	[self failWithError:ASIAuthenticationError];
2617
}
2618

    
2619
- (BOOL)showProxyAuthenticationDialog
2620
{
2621
	if ([self isSynchronous]) {
2622
		return NO;
2623
	}
2624

    
2625
	// Mac authentication dialog coming soon!
2626
	#if TARGET_OS_IPHONE
2627
	if ([self shouldPresentProxyAuthenticationDialog]) {
2628
		[ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
2629
		return YES;
2630
	}
2631
	return NO;
2632
	#else
2633
	return NO;
2634
	#endif
2635
}
2636

    
2637

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

    
2644
	// If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
2645
	// Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
2646
	id authenticationDelegate = [self delegate];
2647
	if (!authenticationDelegate) {
2648
		authenticationDelegate = [self queue];
2649
	}
2650
	
2651
	BOOL delegateOrBlockWillHandleAuthentication = NO;
2652

    
2653
	if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
2654
		delegateOrBlockWillHandleAuthentication = YES;
2655
	}
2656

    
2657
	#if NS_BLOCKS_AVAILABLE
2658
	if(proxyAuthenticationNeededBlock){
2659
		delegateOrBlockWillHandleAuthentication = YES;
2660
	}
2661
	#endif
2662

    
2663
	if (delegateOrBlockWillHandleAuthentication) {
2664
		[self performSelectorOnMainThread:@selector(askDelegateForProxyCredentials) withObject:nil waitUntilDone:NO];
2665
	}
2666
	
2667
	return delegateOrBlockWillHandleAuthentication;
2668
}
2669

    
2670
/* ALWAYS CALLED ON MAIN THREAD! */
2671
- (void)askDelegateForProxyCredentials
2672
{
2673
	id authenticationDelegate = [self delegate];
2674
	if (!authenticationDelegate) {
2675
		authenticationDelegate = [self queue];
2676
	}
2677
	if ([authenticationDelegate respondsToSelector:@selector(proxyAuthenticationNeededForRequest:)]) {
2678
		[authenticationDelegate performSelector:@selector(proxyAuthenticationNeededForRequest:) withObject:self];
2679
		return;
2680
	}
2681
	#if NS_BLOCKS_AVAILABLE
2682
	if(proxyAuthenticationNeededBlock){
2683
		proxyAuthenticationNeededBlock();
2684
	}
2685
	#endif
2686
}
2687

    
2688

    
2689
- (BOOL)willAskDelegateForCredentials
2690
{
2691
	if ([self isSynchronous]) {
2692
		return NO;
2693
	}
2694

    
2695
	// If we have a delegate, we'll see if it can handle proxyAuthenticationNeededForRequest:.
2696
	// Otherwise, we'll try the queue (if this request is part of one) and it will pass the message on to its own delegate
2697
	id authenticationDelegate = [self delegate];
2698
	if (!authenticationDelegate) {
2699
		authenticationDelegate = [self queue];
2700
	}
2701

    
2702
	BOOL delegateOrBlockWillHandleAuthentication = NO;
2703

    
2704
	if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) {
2705
		delegateOrBlockWillHandleAuthentication = YES;
2706
	}
2707

    
2708
	#if NS_BLOCKS_AVAILABLE
2709
	if (authenticationNeededBlock) {
2710
		delegateOrBlockWillHandleAuthentication = YES;
2711
	}
2712
	#endif
2713

    
2714
	if (delegateOrBlockWillHandleAuthentication) {
2715
		[self performSelectorOnMainThread:@selector(askDelegateForCredentials) withObject:nil waitUntilDone:NO];
2716
	}
2717
	return delegateOrBlockWillHandleAuthentication;
2718
}
2719

    
2720
/* ALWAYS CALLED ON MAIN THREAD! */
2721
- (void)askDelegateForCredentials
2722
{
2723
	id authenticationDelegate = [self delegate];
2724
	if (!authenticationDelegate) {
2725
		authenticationDelegate = [self queue];
2726
	}
2727
	
2728
	if ([authenticationDelegate respondsToSelector:@selector(authenticationNeededForRequest:)]) {
2729
		[authenticationDelegate performSelector:@selector(authenticationNeededForRequest:) withObject:self];
2730
		return;
2731
	}
2732
	
2733
	#if NS_BLOCKS_AVAILABLE
2734
	if (authenticationNeededBlock) {
2735
		authenticationNeededBlock();
2736
	}
2737
	#endif	
2738
}
2739

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

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

    
2895
- (BOOL)showAuthenticationDialog
2896
{
2897
	if ([self isSynchronous]) {
2898
		return NO;
2899
	}
2900
	// Mac authentication dialog coming soon!
2901
	#if TARGET_OS_IPHONE
2902
	if ([self shouldPresentAuthenticationDialog]) {
2903
		[ASIAuthenticationDialog performSelectorOnMainThread:@selector(presentAuthenticationDialogForRequest:) withObject:self waitUntilDone:[NSThread isMainThread]];
2904
		return YES;
2905
	}
2906
	return NO;
2907
	#else
2908
	return NO;
2909
	#endif
2910
}
2911

    
2912
- (void)attemptToApplyCredentialsAndResume
2913
{
2914
	if ([self error] || [self isCancelled]) {
2915
		return;
2916
	}
2917
	
2918
	// Do we actually need to authenticate with a proxy?
2919
	if ([self authenticationNeeded] == ASIProxyAuthenticationNeeded) {
2920
		[self attemptToApplyProxyCredentialsAndResume];
2921
		return;
2922
	}
2923
	
2924
	// Read authentication data
2925
	if (!requestAuthentication) {
2926
		CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty((CFReadStreamRef)[self readStream],kCFStreamPropertyHTTPResponseHeader);
2927
		requestAuthentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
2928
		CFRelease(responseHeader);
2929
		[self setAuthenticationScheme:[(NSString *)CFHTTPAuthenticationCopyMethod(requestAuthentication) autorelease]];
2930
	}
2931
	
2932
	if (!requestAuthentication) {
2933
		#if DEBUG_HTTP_AUTHENTICATION
2934
		NSLog(@"[AUTH] Request %@ failed to read authentication information from response headers",self);
2935
		#endif
2936

    
2937
		[self cancelLoad];
2938
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to get authentication object from response headers",NSLocalizedDescriptionKey,nil]]];
2939
		return;
2940
	}
2941
	
2942
	// Get the authentication realm
2943
	[self setAuthenticationRealm:nil];
2944
	if (!CFHTTPAuthenticationRequiresAccountDomain(requestAuthentication)) {
2945
		[self setAuthenticationRealm:[(NSString *)CFHTTPAuthenticationCopyRealm(requestAuthentication) autorelease]];
2946
	}
2947
	
2948
	#if DEBUG_HTTP_AUTHENTICATION
2949
		NSString *realm = [self authenticationRealm];
2950
		if (realm) {
2951
			realm = [NSString stringWithFormat:@" (Realm: %@)",realm];
2952
		} else {
2953
			realm = @"";
2954
		}
2955
		if ([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM || [self authenticationRetryCount] == 0) {
2956
			NSLog(@"[AUTH] Request %@ received 401 challenge and must authenticate using %@%@",self,[self authenticationScheme],realm);
2957
		} else {
2958
			NSLog(@"[AUTH] Request %@ NTLM handshake step %i",self,[self authenticationRetryCount]+1);
2959
		}
2960
	#endif
2961

    
2962
	// See if authentication is valid
2963
	CFStreamError err;		
2964
	if (!CFHTTPAuthenticationIsValid(requestAuthentication, &err)) {
2965
		
2966
		CFRelease(requestAuthentication);
2967
		requestAuthentication = NULL;
2968
		
2969
		// check for bad credentials, so we can give the delegate a chance to replace them
2970
		if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) {
2971

    
2972
			#if DEBUG_HTTP_AUTHENTICATION
2973
			NSLog(@"[AUTH] Request %@ had bad credentials, will remove them from the session store if they are cached",self);
2974
			#endif
2975

    
2976
			// Prevent more than one request from asking for credentials at once
2977
			[delegateAuthenticationLock lock];
2978
			
2979
			// We know the credentials we just presented are bad, we should remove them from the session store too
2980
			[[self class] removeAuthenticationCredentialsFromSessionStore:requestCredentials];
2981
			[self setRequestCredentials:nil];
2982
			
2983
			// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
2984
			if ([self error] || [self isCancelled]) {
2985

    
2986
				#if DEBUG_HTTP_AUTHENTICATION
2987
				NSLog(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self);
2988
				#endif
2989

    
2990
				[delegateAuthenticationLock unlock];
2991
				return;
2992
			}
2993

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

    
2999
					#if DEBUG_HTTP_AUTHENTICATION
3000
					NSLog(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]);
3001
					#endif
3002

    
3003
					[delegateAuthenticationLock unlock];
3004
					[self startRequest];
3005
					return;
3006
				}
3007
			}
3008
			
3009
			[self setLastActivityTime:nil];
3010
			
3011
			if ([self willAskDelegateForCredentials]) {
3012

    
3013
				#if DEBUG_HTTP_AUTHENTICATION
3014
				NSLog(@"[AUTH] Request %@ will ask its delegate for credentials to use",self);
3015
				#endif
3016

    
3017
				[delegateAuthenticationLock unlock];
3018
				return;
3019
			}
3020
			if ([self showAuthenticationDialog]) {
3021

    
3022
				#if DEBUG_HTTP_AUTHENTICATION
3023
				NSLog(@"[AUTH] Request %@ will ask ASIAuthenticationDialog for credentials",self);
3024
				#endif
3025

    
3026
				[delegateAuthenticationLock unlock];
3027
				return;
3028
			}
3029
			[delegateAuthenticationLock unlock];
3030
		}
3031

    
3032
		#if DEBUG_HTTP_AUTHENTICATION
3033
		NSLog(@"[AUTH] Request %@ has no credentials to present and must give up",self);
3034
		#endif
3035

    
3036
		[self cancelLoad];
3037
		[self failWithError:ASIAuthenticationError];
3038
		return;
3039
	}
3040
	
3041
	[self cancelLoad];
3042
	
3043
	if (requestCredentials) {
3044
		
3045
		if ((([self authenticationScheme] != (NSString *)kCFHTTPAuthenticationSchemeNTLM) || [self authenticationRetryCount] < 2) && [self applyCredentials:requestCredentials]) {
3046
			[self startRequest];
3047
			
3048
			// We've failed NTLM authentication twice, we should assume our credentials are wrong
3049
		} else if ([self authenticationScheme] == (NSString *)kCFHTTPAuthenticationSchemeNTLM && [self authenticationRetryCount ] == 2) {
3050
			#if DEBUG_HTTP_AUTHENTICATION
3051
			NSLog(@"[AUTH] Request %@ has failed NTLM authentication",self);
3052
			#endif
3053

    
3054
			[self failWithError:ASIAuthenticationError];
3055
			
3056
		} else {
3057

    
3058
			#if DEBUG_HTTP_AUTHENTICATION
3059
			NSLog(@"[AUTH] Request %@ had credentials and they were not marked as bad, but we got a 401 all the same.",self);
3060
			#endif
3061

    
3062
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
3063
		}
3064
		
3065
		// Are a user name & password needed?
3066
	}  else if (CFHTTPAuthenticationRequiresUserNameAndPassword(requestAuthentication)) {
3067
		
3068
		// Prevent more than one request from asking for credentials at once
3069
		[delegateAuthenticationLock lock];
3070
		
3071
		// If the user cancelled authentication via a dialog presented by another request, our queue may have cancelled us
3072
		if ([self error] || [self isCancelled]) {
3073

    
3074
			#if DEBUG_HTTP_AUTHENTICATION
3075
			NSLog(@"[AUTH] Request %@ failed or was cancelled while waiting to access credentials",self);
3076
			#endif
3077

    
3078
			[delegateAuthenticationLock unlock];
3079
			return;
3080
		}
3081
		
3082
		// Now we've acquired the lock, it may be that the session contains credentials we can re-use for this request
3083
		if ([self useSessionPersistence]) {
3084
			NSDictionary *credentials = [self findSessionAuthenticationCredentials];
3085
			if (credentials && [self applyCredentials:[credentials objectForKey:@"Credentials"]]) {
3086

    
3087
				#if DEBUG_HTTP_AUTHENTICATION
3088
				NSLog(@"[AUTH] Request %@ will reuse cached credentials from the session (%@)",self,[credentials objectForKey:@"AuthenticationScheme"]);
3089
				#endif
3090

    
3091
				[delegateAuthenticationLock unlock];
3092
				[self startRequest];
3093
				return;
3094
			}
3095
		}
3096
		
3097

    
3098
		NSMutableDictionary *newCredentials = [self findCredentials];
3099
		
3100
		//If we have some credentials to use let's apply them to the request and continue
3101
		if (newCredentials) {
3102
			
3103
			if ([self applyCredentials:newCredentials]) {
3104
				[delegateAuthenticationLock unlock];
3105
				[self startRequest];
3106
			} else {
3107
				#if DEBUG_HTTP_AUTHENTICATION
3108
				NSLog(@"[AUTH] Request %@ failed to apply credentials",self);
3109
				#endif
3110
				[delegateAuthenticationLock unlock];
3111
				[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileApplyingCredentialsType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Failed to apply credentials to request",NSLocalizedDescriptionKey,nil]]];
3112
			}
3113
			return;
3114
		}
3115
		if ([self willAskDelegateForCredentials]) {
3116

    
3117
			#if DEBUG_HTTP_AUTHENTICATION
3118
			NSLog(@"[AUTH] Request %@ will ask its delegate for credentials to use",self);
3119
			#endif
3120

    
3121
			[delegateAuthenticationLock unlock];
3122
			return;
3123
		}
3124
		if ([self showAuthenticationDialog]) {
3125

    
3126
			#if DEBUG_HTTP_AUTHENTICATION
3127
			NSLog(@"[AUTH] Request %@ will ask ASIAuthenticationDialog for credentials",self);
3128
			#endif
3129

    
3130
			[delegateAuthenticationLock unlock];
3131
			return;
3132
		}
3133

    
3134
		#if DEBUG_HTTP_AUTHENTICATION
3135
		NSLog(@"[AUTH] Request %@ has no credentials to present and must give up",self);
3136
		#endif
3137
		[delegateAuthenticationLock unlock];
3138
		[self failWithError:ASIAuthenticationError];
3139
		return;
3140
	}
3141
	
3142
}
3143

    
3144
- (void)addBasicAuthenticationHeaderWithUsername:(NSString *)theUsername andPassword:(NSString *)thePassword
3145
{
3146
	[self addRequestHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@",[ASIHTTPRequest base64forData:[[NSString stringWithFormat:@"%@:%@",theUsername,thePassword] dataUsingEncoding:NSUTF8StringEncoding]]]];	
3147
	[self setAuthenticationScheme:(NSString *)kCFHTTPAuthenticationSchemeBasic];
3148

    
3149
}
3150

    
3151

    
3152
#pragma mark stream status handlers
3153

    
3154
- (void)handleNetworkEvent:(CFStreamEventType)type
3155
{	
3156
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
3157

    
3158
	[[self cancelledLock] lock];
3159
	
3160
	if ([self complete] || [self isCancelled]) {
3161
		[[self cancelledLock] unlock];
3162
		[pool release];
3163
		return;
3164
	}
3165

    
3166
	CFRetain(self);
3167

    
3168
    // Dispatch the stream events.
3169
    switch (type) {
3170
        case kCFStreamEventHasBytesAvailable:
3171
            [self handleBytesAvailable];
3172
            break;
3173
            
3174
        case kCFStreamEventEndEncountered:
3175
            [self handleStreamComplete];
3176
            break;
3177
            
3178
        case kCFStreamEventErrorOccurred:
3179
            [self handleStreamError];
3180
            break;
3181
            
3182
        default:
3183
            break;
3184
    }
3185
	
3186
	[self performThrottling];
3187
	
3188
	[[self cancelledLock] unlock];
3189
	
3190
	if ([self downloadComplete] && [self needsRedirect]) {
3191
		if (![self willAskDelegateToConfirmRedirect]) {
3192
			[self performRedirect];
3193
		}
3194
	} else if ([self downloadComplete] && [self authenticationNeeded]) {
3195
		[self attemptToApplyCredentialsAndResume];
3196
	}
3197

    
3198
	CFRelease(self);
3199
	[pool release];
3200
}
3201

    
3202
- (BOOL)willAskDelegateToConfirmRedirect
3203
{
3204
	// We must lock to ensure delegate / queue aren't changed while we check them
3205
	[[self cancelledLock] lock];
3206

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

    
3211
	[[self cancelledLock] unlock];
3212

    
3213
	// Either the delegate or the queue's delegate is interested in being told when we are about to redirect
3214
	if (needToAskDelegateAboutRedirect) {
3215
		NSURL *newURL = [[[self redirectURL] copy] autorelease];
3216
		[self setRedirectURL:nil];
3217
		[self performSelectorOnMainThread:@selector(requestWillRedirectToURL:) withObject:newURL waitUntilDone:[NSThread isMainThread]];
3218
		return true;
3219
	}
3220
	return false;
3221
}
3222

    
3223
- (void)handleBytesAvailable
3224
{
3225
	if (![self responseHeaders]) {
3226
		[self readResponseHeaders];
3227
	}
3228
	
3229
	// If we've cancelled the load part way through (for example, after deciding to use a cached version)
3230
	if ([self complete]) {
3231
		return;
3232
	}
3233
	
3234
	// In certain (presumably very rare) circumstances, handleBytesAvailable seems to be called when there isn't actually any data available
3235
	// We'll check that there is actually data available to prevent blocking on CFReadStreamRead()
3236
	// So far, I've only seen this in the stress tests, so it might never happen in real-world situations.
3237
	if (!CFReadStreamHasBytesAvailable((CFReadStreamRef)[self readStream])) {
3238
		return;
3239
	}
3240

    
3241
	long long bufferSize = 16384;
3242
	if (contentLength > 262144) {
3243
		bufferSize = 262144;
3244
	} else if (contentLength > 65536) {
3245
		bufferSize = 65536;
3246
	}
3247
	
3248
	// Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active
3249
	// This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit
3250
	
3251
	if ([[self class] isBandwidthThrottled]) {
3252
		[bandwidthThrottlingLock lock];
3253
		if (maxBandwidthPerSecond > 0) {
3254
			long long maxiumumSize  = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
3255
			if (maxiumumSize < 0) {
3256
				// 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
3257
				bufferSize = 1;
3258
			} else if (maxiumumSize/4 < bufferSize) {
3259
				// We were going to fetch more data that we should be allowed, so we'll reduce the size of our read
3260
				bufferSize = maxiumumSize/4;
3261
			}
3262
		}
3263
		if (bufferSize < 1) {
3264
			bufferSize = 1;
3265
		}
3266
		[bandwidthThrottlingLock unlock];
3267
	}
3268
	
3269
	
3270
    UInt8 buffer[bufferSize];
3271
    NSInteger bytesRead = [[self readStream] read:buffer maxLength:sizeof(buffer)];
3272

    
3273
    // Less than zero is an error
3274
    if (bytesRead < 0) {
3275
        [self handleStreamError];
3276
		
3277
	// If zero bytes were read, wait for the EOF to come.
3278
    } else if (bytesRead) {
3279

    
3280
		// If we are inflating the response on the fly
3281
		NSData *inflatedData = nil;
3282
		if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3283
			if (![self dataDecompressor]) {
3284
				[self setDataDecompressor:[ASIDataDecompressor decompressor]];
3285
			}
3286
			NSError *err = nil;
3287
			inflatedData = [[self dataDecompressor] uncompressBytes:buffer length:bytesRead error:&err];
3288
			if (err) {
3289
				[self failWithError:err];
3290
				return;
3291
			}
3292
		}
3293
		
3294
		[self setTotalBytesRead:[self totalBytesRead]+bytesRead];
3295
		[self setLastActivityTime:[NSDate date]];
3296

    
3297
		// For bandwidth measurement / throttling
3298
		[ASIHTTPRequest incrementBandwidthUsedInLastSecond:bytesRead];
3299
		
3300
		// If we need to redirect, and have automatic redirect on, and might be resuming a download, let's do nothing with the content
3301
		if ([self needsRedirect] && [self shouldRedirect] && [self allowResumeForFileDownloads]) {
3302
			return;
3303
		}
3304
		
3305
		BOOL dataWillBeHandledExternally = NO;
3306
		if ([[self delegate] respondsToSelector:[self didReceiveDataSelector]]) {
3307
			dataWillBeHandledExternally = YES;
3308
		}
3309
		#if NS_BLOCKS_AVAILABLE
3310
		if (dataReceivedBlock) {
3311
			dataWillBeHandledExternally = YES;
3312
		}
3313
		#endif
3314
		// Does the delegate want to handle the data manually?
3315
		if (dataWillBeHandledExternally) {
3316

    
3317
			NSData *data = nil;
3318
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3319
				data = inflatedData;
3320
			} else {
3321
				data = [NSData dataWithBytes:buffer length:bytesRead];
3322
			}
3323
			[self performSelectorOnMainThread:@selector(passOnReceivedData:) withObject:data waitUntilDone:[NSThread isMainThread]];
3324
			
3325
		// Are we downloading to a file?
3326
		} else if ([self downloadDestinationPath]) {
3327
			BOOL append = NO;
3328
			if (![self fileDownloadOutputStream]) {
3329
				if (![self temporaryFileDownloadPath]) {
3330
					[self setTemporaryFileDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
3331
				} else if ([self allowResumeForFileDownloads] && [[self requestHeaders] objectForKey:@"Range"]) {
3332
					if ([[self responseHeaders] objectForKey:@"Content-Range"]) {
3333
						append = YES;
3334
					} else {
3335
						[self incrementDownloadSizeBy:-[self partialDownloadSize]];
3336
						[self setPartialDownloadSize:0];
3337
					}
3338
				}
3339

    
3340
				[self setFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryFileDownloadPath] append:append] autorelease]];
3341
				[[self fileDownloadOutputStream] open];
3342

    
3343
			}
3344
			[[self fileDownloadOutputStream] write:buffer maxLength:bytesRead];
3345

    
3346
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3347
				
3348
				if (![self inflatedFileDownloadOutputStream]) {
3349
					if (![self temporaryUncompressedDataDownloadPath]) {
3350
						[self setTemporaryUncompressedDataDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
3351
					}
3352
					
3353
					[self setInflatedFileDownloadOutputStream:[[[NSOutputStream alloc] initToFileAtPath:[self temporaryUncompressedDataDownloadPath] append:append] autorelease]];
3354
					[[self inflatedFileDownloadOutputStream] open];
3355
				}
3356

    
3357
				[[self inflatedFileDownloadOutputStream] write:[inflatedData bytes] maxLength:[inflatedData length]];
3358
			}
3359

    
3360
			
3361
		//Otherwise, let's add the data to our in-memory store
3362
		} else {
3363
			if ([self isResponseCompressed] && ![self shouldWaitToInflateCompressedResponses]) {
3364
				[rawResponseData appendData:inflatedData];
3365
			} else {
3366
				[rawResponseData appendBytes:buffer length:bytesRead];
3367
			}
3368
		}
3369
    }
3370
}
3371

    
3372
- (void)handleStreamComplete
3373
{	
3374

    
3375
#if DEBUG_REQUEST_STATUS
3376
	NSLog(@"[STATUS] Request %@ finished downloading data (%qu bytes)",self, [self totalBytesRead]);
3377
#endif
3378
	[self setStatusTimer:nil];
3379
	[self setDownloadComplete:YES];
3380
	
3381
	if (![self responseHeaders]) {
3382
		[self readResponseHeaders];
3383
	}
3384

    
3385
	[progressLock lock];	
3386
	// Find out how much data we've uploaded so far
3387
	[self setLastBytesSent:totalBytesSent];	
3388
	[self setTotalBytesSent:[NSMakeCollectable([(NSNumber *)CFReadStreamCopyProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPRequestBytesWrittenCount) autorelease]) unsignedLongLongValue]];
3389
	[self setComplete:YES];
3390
	if (![self contentLength]) {
3391
		[self setContentLength:[self totalBytesRead]];
3392
	}
3393
	[self updateProgressIndicators];
3394

    
3395
	
3396
	[[self postBodyReadStream] close];
3397
	[self setPostBodyReadStream:nil];
3398
	
3399
	[self setDataDecompressor:nil];
3400

    
3401
	NSError *fileError = nil;
3402
	
3403
	// Delete up the request body temporary file, if it exists
3404
	if ([self didCreateTemporaryPostDataFile] && ![self authenticationNeeded]) {
3405
		[self removeTemporaryUploadFile];
3406
		[self removeTemporaryCompressedUploadFile];
3407
	}
3408
	
3409
	// Close the output stream as we're done writing to the file
3410
	if ([self temporaryFileDownloadPath]) {
3411
		
3412
		[[self fileDownloadOutputStream] close];
3413
		[self setFileDownloadOutputStream:nil];
3414

    
3415
		[[self inflatedFileDownloadOutputStream] close];
3416
		[self setInflatedFileDownloadOutputStream:nil];
3417

    
3418
		// If we are going to redirect and we are resuming, let's ignore this download
3419
		if ([self shouldRedirect] && [self needsRedirect] && [self allowResumeForFileDownloads]) {
3420
		
3421
		} else if ([self isResponseCompressed]) {
3422
			
3423
			// Decompress the file directly to the destination path
3424
			if ([self shouldWaitToInflateCompressedResponses]) {
3425
				[ASIDataDecompressor uncompressDataFromFile:[self temporaryFileDownloadPath] toFile:[self downloadDestinationPath] error:&fileError];
3426

    
3427
			// Response should already have been inflated, move the temporary file to the destination path
3428
			} else {
3429
				NSError *moveError = nil;
3430
				[[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryUncompressedDataDownloadPath] toPath:[self downloadDestinationPath] error:&moveError];
3431
				if (moveError) {
3432
					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]];
3433
				}
3434
				[self setTemporaryUncompressedDataDownloadPath:nil];
3435

    
3436
			}
3437
			[self removeTemporaryDownloadFile];
3438

    
3439
		} else {
3440
	
3441
			//Remove any file at the destination path
3442
			NSError *moveError = nil;
3443
			if (![[self class] removeFileAtPath:[self downloadDestinationPath] error:&moveError]) {
3444
				fileError = moveError;
3445

    
3446
			}
3447

    
3448
			//Move the temporary file to the destination path
3449
			if (!fileError) {
3450
				[[[[NSFileManager alloc] init] autorelease] moveItemAtPath:[self temporaryFileDownloadPath] 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 setTemporaryFileDownloadPath:nil];
3455
			}
3456
			
3457
		}
3458
	}
3459
	
3460
	// Save to the cache
3461
	if ([self downloadCache] && ![self didUseCachedResponse]) {
3462
		[[self downloadCache] storeResponseForRequest:self maxAge:[self secondsToCache]];
3463
	}
3464
	
3465
	[progressLock unlock];
3466

    
3467
	
3468
	[connectionsLock lock];
3469
	if (![self connectionCanBeReused]) {
3470
		[self unscheduleReadStream];
3471
	}
3472
	#if DEBUG_PERSISTENT_CONNECTIONS
3473
	if ([self requestID]) {
3474
		NSLog(@"[CONNECTION] Request #%@ finished using connection #%@",[self requestID], [[self connectionInfo] objectForKey:@"id"]);
3475
	}
3476
	#endif
3477
	[[self connectionInfo] removeObjectForKey:@"request"];
3478
	[[self connectionInfo] setObject:[NSDate dateWithTimeIntervalSinceNow:[self persistentConnectionTimeoutSeconds]] forKey:@"expires"];
3479
	[connectionsLock unlock];
3480
	
3481
	if (![self authenticationNeeded]) {
3482
		[self destroyReadStream];
3483
	}
3484
	
3485

    
3486
	if (![self needsRedirect] && ![self authenticationNeeded] && ![self didUseCachedResponse]) {
3487
		
3488
		if (fileError) {
3489
			[self failWithError:fileError];
3490
		} else {
3491
			[self requestFinished];
3492
		}
3493

    
3494
		[self markAsFinished];
3495
		
3496
	// If request has asked delegate or ASIAuthenticationDialog for credentials
3497
	} else if ([self authenticationNeeded]) {
3498
		CFRunLoopStop(CFRunLoopGetCurrent());
3499
	}
3500

    
3501
}
3502

    
3503
- (void)markAsFinished
3504
{
3505
	// Autoreleased requests may well be dealloced here otherwise
3506
	CFRetain(self);
3507

    
3508
	// dealloc won't be called when running with GC, so we'll clean these up now
3509
	if (request) {
3510
		CFMakeCollectable(request);
3511
	}
3512
	if (requestAuthentication) {
3513
		CFMakeCollectable(requestAuthentication);
3514
	}
3515
	if (proxyAuthentication) {
3516
		CFMakeCollectable(proxyAuthentication);
3517
	}
3518

    
3519
    BOOL wasInProgress = inProgress;
3520
    BOOL wasFinished = finished;
3521

    
3522
    if (!wasFinished)
3523
        [self willChangeValueForKey:@"isFinished"];
3524
    if (wasInProgress)
3525
        [self willChangeValueForKey:@"isExecuting"];
3526

    
3527
	[self setInProgress:NO];
3528
    finished = YES;
3529

    
3530
    if (wasInProgress)
3531
        [self didChangeValueForKey:@"isExecuting"];
3532
    if (!wasFinished)
3533
        [self didChangeValueForKey:@"isFinished"];
3534

    
3535
	CFRunLoopStop(CFRunLoopGetCurrent());
3536

    
3537
	#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
3538
	if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
3539
		dispatch_async(dispatch_get_main_queue(), ^{
3540
			if (backgroundTask != UIBackgroundTaskInvalid) {
3541
				[[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
3542
				backgroundTask = UIBackgroundTaskInvalid;
3543
			}
3544
		});
3545
	}
3546
	#endif
3547
	CFRelease(self);
3548
}
3549

    
3550
- (void)useDataFromCache
3551
{
3552
	NSDictionary *headers = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
3553
	NSString *dataPath = [[self downloadCache] pathToCachedResponseDataForURL:[self url]];
3554

    
3555
	ASIHTTPRequest *theRequest = self;
3556
	if ([self mainRequest]) {
3557
		theRequest = [self mainRequest];
3558
	}
3559

    
3560
	if (headers && dataPath) {
3561

    
3562
		[self setResponseStatusCode:[[headers objectForKey:@"X-ASIHTTPRequest-Response-Status-Code"] intValue]];
3563
		[self setDidUseCachedResponse:YES];
3564
		[theRequest setResponseHeaders:headers];
3565

    
3566
		if ([theRequest downloadDestinationPath]) {
3567
			[theRequest setDownloadDestinationPath:dataPath];
3568
		} else {
3569
			[theRequest setRawResponseData:[NSMutableData dataWithData:[[self downloadCache] cachedResponseDataForURL:[self url]]]];
3570
		}
3571
		[theRequest setContentLength:[[[self responseHeaders] objectForKey:@"Content-Length"] longLongValue]];
3572
		[theRequest setTotalBytesRead:[self contentLength]];
3573

    
3574
		[theRequest parseStringEncodingFromHeaders];
3575

    
3576
		[theRequest setResponseCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:headers forURL:[self url]]];
3577

    
3578
		// See if we need to redirect
3579
		if ([self willRedirect]) {
3580
			if (![self willAskDelegateToConfirmRedirect]) {
3581
				[self performRedirect];
3582
			}
3583
			return;
3584
		}
3585
	}
3586

    
3587
	[theRequest setComplete:YES];
3588
	[theRequest setDownloadComplete:YES];
3589

    
3590
	// If we're pulling data from the cache without contacting the server at all, we won't have set originalURL yet
3591
	if ([self redirectCount] == 0) {
3592
		[theRequest setOriginalURL:[theRequest url]];
3593
	}
3594

    
3595
	[theRequest updateProgressIndicators];
3596
	[theRequest requestFinished];
3597
	[theRequest markAsFinished];	
3598
	if ([self mainRequest]) {
3599
		[self markAsFinished];
3600
	}
3601
}
3602

    
3603
- (BOOL)retryUsingNewConnection
3604
{
3605
	if ([self retryCount] == 0) {
3606

    
3607
		[self setWillRetryRequest:YES];
3608
		[self cancelLoad];
3609
		[self setWillRetryRequest:NO];
3610

    
3611
		#if DEBUG_PERSISTENT_CONNECTIONS
3612
			NSLog(@"[CONNECTION] Request attempted to use connection #%@, but it has been closed - will retry with a new connection", [[self connectionInfo] objectForKey:@"id"]);
3613
		#endif
3614
		[connectionsLock lock];
3615
		[[self connectionInfo] removeObjectForKey:@"request"];
3616
		[persistentConnectionsPool removeObject:[self connectionInfo]];
3617
		[self setConnectionInfo:nil];
3618
		[connectionsLock unlock];
3619
		[self setRetryCount:[self retryCount]+1];
3620
		[self startRequest];
3621
		return YES;
3622
	}
3623
	#if DEBUG_PERSISTENT_CONNECTIONS
3624
		NSLog(@"[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"]);
3625
	#endif	
3626
	return NO;
3627
}
3628

    
3629
- (void)handleStreamError
3630

    
3631
{
3632
	NSError *underlyingError = NSMakeCollectable([(NSError *)CFReadStreamCopyError((CFReadStreamRef)[self readStream]) autorelease]);
3633

    
3634
	if (![self error]) { // We may already have handled this error
3635
		
3636
		// First, check for a 'socket not connected', 'broken pipe' or 'connection lost' error
3637
		// This may occur when we've attempted to reuse a connection that should have been closed
3638
		// If we get this, we need to retry the request
3639
		// We'll only do this once - if it happens again on retry, we'll give up
3640
		// -1005 = kCFURLErrorNetworkConnectionLost - this doesn't seem to be declared on Mac OS 10.5
3641
		if (([[underlyingError domain] isEqualToString:NSPOSIXErrorDomain] && ([underlyingError code] == ENOTCONN || [underlyingError code] == EPIPE)) 
3642
			|| ([[underlyingError domain] isEqualToString:(NSString *)kCFErrorDomainCFNetwork] && [underlyingError code] == -1005)) {
3643
			if ([self retryUsingNewConnection]) {
3644
				return;
3645
			}
3646
		}
3647
		
3648
		NSString *reason = @"A connection failure occurred";
3649
		
3650
		// We'll use a custom error message for SSL errors, but you should always check underlying error if you want more details
3651
		// For some reason SecureTransport.h doesn't seem to be available on iphone, so error codes hard-coded
3652
		// 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
3653
		if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) {
3654
			if ([underlyingError code] <= -9800 && [underlyingError code] >= -9818) {
3655
				reason = [NSString stringWithFormat:@"%@: SSL problem (Possible causes may include a bad/expired/self-signed certificate, clock set to wrong date)",reason];
3656
			}
3657
		}
3658
		[self cancelLoad];
3659
		[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]];
3660
	} else {
3661
		[self cancelLoad];
3662
	}
3663
	[self checkRequestStatus];
3664
}
3665

    
3666
#pragma mark managing the read stream
3667

    
3668
- (void)destroyReadStream
3669
{
3670
    if ([self readStream]) {
3671
		[self unscheduleReadStream];
3672
		if (![self connectionCanBeReused]) {
3673
			[[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3674
			[[self readStream] close];
3675
		}
3676
		[self setReadStream:nil];
3677
    }	
3678
}
3679

    
3680
- (void)scheduleReadStream
3681
{
3682
	if ([self readStream] && ![self readStreamIsScheduled]) {
3683

    
3684
		[connectionsLock lock];
3685
		runningRequestCount++;
3686
		if (shouldUpdateNetworkActivityIndicator) {
3687
			[[self class] showNetworkActivityIndicator];
3688
		}
3689
		[connectionsLock unlock];
3690

    
3691
		// Reset the timeout
3692
		[self setLastActivityTime:[NSDate date]];
3693
		CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
3694
		CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt);
3695
		[[self readStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3696
		[self setReadStreamIsScheduled:YES];
3697
	}
3698
}
3699

    
3700

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

    
3705
		[connectionsLock lock];
3706
		runningRequestCount--;
3707
		if (shouldUpdateNetworkActivityIndicator && runningRequestCount == 0) {
3708
			// This call will wait half a second before turning off the indicator
3709
			// This can prevent flicker when you have a single request finish and then immediately start another request
3710
			// We run this on the main thread because we have no guarantee this thread will have a runloop in 0.5 seconds time
3711
			// 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
3712
			[[self class] performSelectorOnMainThread:@selector(hideNetworkActivityIndicatorAfterDelay) withObject:nil waitUntilDone:[NSThread isMainThread]];
3713
		}
3714
		[connectionsLock unlock];
3715

    
3716
		CFReadStreamSetClient((CFReadStreamRef)[self readStream], kCFStreamEventNone, NULL, NULL);
3717
		[[self readStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3718
		[self setReadStreamIsScheduled:NO];
3719
	}
3720
}
3721

    
3722
#pragma mark cleanup
3723

    
3724
- (BOOL)removeTemporaryDownloadFile
3725
{
3726
	NSError *err = nil;
3727
	if ([self temporaryFileDownloadPath]) {
3728
		if (![[self class] removeFileAtPath:[self temporaryFileDownloadPath] error:&err]) {
3729
			[self failWithError:err];
3730
		}
3731
		[self setTemporaryFileDownloadPath:nil];
3732
	}
3733
	return (!err);
3734
}
3735

    
3736
- (BOOL)removeTemporaryUncompressedDownloadFile
3737
{
3738
	NSError *err = nil;
3739
	if ([self temporaryUncompressedDataDownloadPath]) {
3740
		if (![[self class] removeFileAtPath:[self temporaryUncompressedDataDownloadPath] error:&err]) {
3741
			[self failWithError:err];
3742
		}
3743
		[self setTemporaryUncompressedDataDownloadPath:nil];
3744
	}
3745
	return (!err);
3746
}
3747

    
3748
- (BOOL)removeTemporaryUploadFile
3749
{
3750
	NSError *err = nil;
3751
	if ([self postBodyFilePath]) {
3752
		if (![[self class] removeFileAtPath:[self postBodyFilePath] error:&err]) {
3753
			[self failWithError:err];
3754
		}
3755
		[self setPostBodyFilePath:nil];
3756
	}
3757
	return (!err);
3758
}
3759

    
3760
- (BOOL)removeTemporaryCompressedUploadFile
3761
{
3762
	NSError *err = nil;
3763
	if ([self compressedPostBodyFilePath]) {
3764
		if (![[self class] removeFileAtPath:[self compressedPostBodyFilePath] error:&err]) {
3765
			[self failWithError:err];
3766
		}
3767
		[self setCompressedPostBodyFilePath:nil];
3768
	}
3769
	return (!err);
3770
}
3771

    
3772
+ (BOOL)removeFileAtPath:(NSString *)path error:(NSError **)err
3773
{
3774
	NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
3775

    
3776
	if ([fileManager fileExistsAtPath:path]) {
3777
		NSError *removeError = nil;
3778
		[fileManager removeItemAtPath:path error:&removeError];
3779
		if (removeError) {
3780
			if (err) {
3781
				*err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at path '%@'",path],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]];
3782
			}
3783
			return NO;
3784
		}
3785
	}
3786
	return YES;
3787
}
3788

    
3789
#pragma mark Proxies
3790

    
3791
- (BOOL)configureProxies
3792
{
3793
	// Have details of the proxy been set on this request
3794
	if (![self isPACFileRequest] && (![self proxyHost] && ![self proxyPort])) {
3795

    
3796
		// If not, we need to figure out what they'll be
3797
		NSArray *proxies = nil;
3798

    
3799
		// Have we been given a proxy auto config file?
3800
		if ([self PACurl]) {
3801

    
3802
			// If yes, we'll need to fetch the PAC file asynchronously, so we stop this request to wait until we have the proxy details.
3803
			[self fetchPACFile];
3804
			return NO;
3805

    
3806
			// Detect proxy settings and apply them
3807
		} else {
3808

    
3809
#if TARGET_OS_IPHONE
3810
			NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]);
3811
#else
3812
			NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)SCDynamicStoreCopyProxies(NULL) autorelease]);
3813
#endif
3814

    
3815
			proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[self url], (CFDictionaryRef)proxySettings) autorelease]);
3816

    
3817
			// 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
3818
			NSDictionary *settings = [proxies objectAtIndex:0];
3819
			if ([settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]) {
3820
				[self setPACurl:[settings objectForKey:(NSString *)kCFProxyAutoConfigurationURLKey]];
3821
				[self fetchPACFile];
3822
				return NO;
3823
			}
3824
		}
3825

    
3826
		if (!proxies) {
3827
			[self setReadStream:nil];
3828
			[self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to obtain information on proxy servers needed for request",NSLocalizedDescriptionKey,nil]]];
3829
			return NO;
3830
		}
3831
		// I don't really understand why the dictionary returned by CFNetworkCopyProxiesForURL uses different key names from CFNetworkCopySystemProxySettings/SCDynamicStoreCopyProxies
3832
		// 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)
3833
		if ([proxies count] > 0) {
3834
			NSDictionary *settings = [proxies objectAtIndex:0];
3835
			[self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]];
3836
			[self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]];
3837
			[self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]];
3838
		}
3839
	}
3840
	return YES;
3841
}
3842

    
3843

    
3844

    
3845
// Attempts to download a PAC (Proxy Auto-Configuration) file
3846
// PAC files at file://, http:// and https:// addresses are supported
3847
- (void)fetchPACFile
3848
{
3849
	// For file:// urls, we'll use an async NSInputStream (ASIHTTPRequest does not support file:// urls)
3850
	if ([[self PACurl] isFileURL]) {
3851
		NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:[[self PACurl] path]] autorelease];
3852
		[self setPACFileReadStream:stream];
3853
		[stream setDelegate:(id)self];
3854
		[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3855
		[stream open];
3856
		// If it takes more than timeOutSeconds to read the PAC, we'll just give up and assume no proxies
3857
		// We won't bother to handle cases where the first part of the PAC is read within timeOutSeconds, but the whole thing takes longer
3858
		// 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
3859
		[self performSelector:@selector(timeOutPACRead) withObject:nil afterDelay:[self timeOutSeconds]];
3860
		return;
3861
	}
3862

    
3863
	NSString *scheme = [[[self PACurl] scheme] lowercaseString];
3864
	if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) {
3865
		// Don't know how to read data from this URL, we'll have to give up
3866
		// We'll simply assume no proxies, and start the request as normal
3867
		[self startRequest];
3868
		return;
3869
	}
3870

    
3871
	// Create an ASIHTTPRequest to fetch the PAC file
3872
	ASIHTTPRequest *PACRequest = [ASIHTTPRequest requestWithURL:[self PACurl]];
3873

    
3874
	// Will prevent this request attempting to configure proxy settings for itself
3875
	[PACRequest setIsPACFileRequest:YES];
3876

    
3877
	[PACRequest setTimeOutSeconds:[self timeOutSeconds]];
3878

    
3879
	// If we're a synchronous request, we'll download the PAC file synchronously
3880
	if ([self isSynchronous]) {
3881
		[PACRequest startSynchronous];
3882
		if (![PACRequest error] && [PACRequest responseString]) {
3883
			[self runPACScript:[PACRequest responseString]];
3884
		}
3885
		[self startRequest];
3886
		return;
3887
	}
3888

    
3889
	[self setPACFileRequest:PACRequest];
3890

    
3891
	// Force this request to run before others in the shared queue
3892
	[PACRequest setQueuePriority:NSOperationQueuePriorityHigh];
3893

    
3894
	// 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
3895
	[PACRequest setDelegate:self];
3896
	[PACRequest setDidFinishSelector:@selector(finishedDownloadingPACFile:)];
3897
	[PACRequest setDidFailSelector:@selector(finishedDownloadingPACFile:)];
3898
	[PACRequest startAsynchronous];
3899

    
3900
	// Temporarily increase the number of operations in the shared queue to give our request a chance to run
3901
	[connectionsLock lock];
3902
	[sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]+1];
3903
	[connectionsLock unlock];
3904
}
3905

    
3906
// Called as we read the PAC file from a file:// url
3907
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
3908
{
3909
	if (![self PACFileReadStream]) {
3910
		return;
3911
	}
3912
	if (eventCode == NSStreamEventHasBytesAvailable) {
3913

    
3914
		if (![self PACFileData]) {
3915
			[self setPACFileData:[NSMutableData data]];
3916
		}
3917
		// If your PAC file is larger than 16KB, you're just being cruel.
3918
		uint8_t buf[16384];
3919
		NSInteger len = [(NSInputStream *)stream read:buf maxLength:16384];
3920
		if (len) {
3921
			[[self PACFileData] appendBytes:(const void *)buf length:len];
3922
		}
3923

    
3924
	} else if (eventCode == NSStreamEventErrorOccurred || eventCode == NSStreamEventEndEncountered) {
3925

    
3926
		[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutPACRead) object:nil];
3927

    
3928
		[stream close];
3929
		[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:[self runLoopMode]];
3930
		[self setPACFileReadStream:nil];
3931

    
3932
		if (eventCode == NSStreamEventEndEncountered) {
3933
			// It sounds as though we have no idea what encoding a PAC file will use
3934
			static NSStringEncoding encodingsToTry[2] = {NSUTF8StringEncoding,NSISOLatin1StringEncoding};
3935
			NSUInteger i;
3936
			for (i=0; i<2; i++) {
3937
				NSString *pacScript =  [[[NSString alloc] initWithBytes:[[self PACFileData] bytes] length:[[self PACFileData] length] encoding:encodingsToTry[i]] autorelease];
3938
				if (pacScript) {
3939
					[self runPACScript:pacScript];
3940
					break;
3941
				}
3942
			}
3943
		}
3944
		[self setPACFileData:nil];
3945
		[self startRequest];
3946
	}
3947
}
3948

    
3949
// Called if it takes longer than timeOutSeconds to read the whole PAC file (when reading from a file:// url)
3950
- (void)timeOutPACRead
3951
{
3952
	[self stream:[self PACFileReadStream] handleEvent:NSStreamEventErrorOccurred];
3953
}
3954

    
3955
// Runs the downloaded PAC script
3956
- (void)runPACScript:(NSString *)script
3957
{
3958
	if (script) {
3959
		// From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
3960
		// Work around <rdar://problem/5530166>.  This dummy call to 
3961
		// CFNetworkCopyProxiesForURL initialise some state within CFNetwork 
3962
		// that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
3963
		CFRelease(CFNetworkCopyProxiesForURL((CFURLRef)[self url], NULL));
3964

    
3965
		// Obtain the list of proxies by running the autoconfiguration script
3966
		CFErrorRef err = NULL;
3967
		NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForAutoConfigurationScript((CFStringRef)script,(CFURLRef)[self url], &err) autorelease]);
3968
		if (!err && [proxies count] > 0) {
3969
			NSDictionary *settings = [proxies objectAtIndex:0];
3970
			[self setProxyHost:[settings objectForKey:(NSString *)kCFProxyHostNameKey]];
3971
			[self setProxyPort:[[settings objectForKey:(NSString *)kCFProxyPortNumberKey] intValue]];
3972
			[self setProxyType:[settings objectForKey:(NSString *)kCFProxyTypeKey]];
3973
		}
3974
	}
3975
}
3976

    
3977
// Called if we successfully downloaded a PAC file from a webserver
3978
- (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest
3979
{
3980
	if (![theRequest error] && [theRequest responseString]) {
3981
		[self runPACScript:[theRequest responseString]];
3982
	}
3983

    
3984
	// Set the shared queue's maxConcurrentOperationCount back to normal
3985
	[connectionsLock lock];
3986
	[sharedQueue setMaxConcurrentOperationCount:[sharedQueue maxConcurrentOperationCount]-1];
3987
	[connectionsLock unlock];
3988

    
3989
	// We no longer need our PAC file request
3990
	[self setPACFileRequest:nil];
3991

    
3992
	// Start the request
3993
	[self startRequest];
3994
}
3995

    
3996

    
3997
#pragma mark persistent connections
3998

    
3999
- (NSNumber *)connectionID
4000
{
4001
	return [[self connectionInfo] objectForKey:@"id"];
4002
}
4003

    
4004
+ (void)expirePersistentConnections
4005
{
4006
	[connectionsLock lock];
4007
	NSUInteger i;
4008
	for (i=0; i<[persistentConnectionsPool count]; i++) {
4009
		NSDictionary *existingConnection = [persistentConnectionsPool objectAtIndex:i];
4010
		if (![existingConnection objectForKey:@"request"] && [[existingConnection objectForKey:@"expires"] timeIntervalSinceNow] <= 0) {
4011
#if DEBUG_PERSISTENT_CONNECTIONS
4012
			NSLog(@"[CONNECTION] Closing connection #%i because it has expired",[[existingConnection objectForKey:@"id"] intValue]);
4013
#endif
4014
			NSInputStream *stream = [existingConnection objectForKey:@"stream"];
4015
			if (stream) {
4016
				[stream close];
4017
			}
4018
			[persistentConnectionsPool removeObject:existingConnection];
4019
			i--;
4020
		}
4021
	}	
4022
	[connectionsLock unlock];
4023
}
4024

    
4025
#pragma mark NSCopying
4026

    
4027
- (id)copyWithZone:(NSZone *)zone
4028
{
4029
	// Don't forget - this will return a retained copy!
4030
	ASIHTTPRequest *newRequest = [[[self class] alloc] initWithURL:[self url]];
4031
	[newRequest setDelegate:[self delegate]];
4032
	[newRequest setRequestMethod:[self requestMethod]];
4033
	[newRequest setPostBody:[self postBody]];
4034
	[newRequest setShouldStreamPostDataFromDisk:[self shouldStreamPostDataFromDisk]];
4035
	[newRequest setPostBodyFilePath:[self postBodyFilePath]];
4036
	[newRequest setRequestHeaders:[[[self requestHeaders] mutableCopyWithZone:zone] autorelease]];
4037
	[newRequest setRequestCookies:[[[self requestCookies] mutableCopyWithZone:zone] autorelease]];
4038
	[newRequest setUseCookiePersistence:[self useCookiePersistence]];
4039
	[newRequest setUseKeychainPersistence:[self useKeychainPersistence]];
4040
	[newRequest setUseSessionPersistence:[self useSessionPersistence]];
4041
	[newRequest setAllowCompressedResponse:[self allowCompressedResponse]];
4042
	[newRequest setDownloadDestinationPath:[self downloadDestinationPath]];
4043
	[newRequest setTemporaryFileDownloadPath:[self temporaryFileDownloadPath]];
4044
	[newRequest setUsername:[self username]];
4045
	[newRequest setPassword:[self password]];
4046
	[newRequest setDomain:[self domain]];
4047
	[newRequest setProxyUsername:[self proxyUsername]];
4048
	[newRequest setProxyPassword:[self proxyPassword]];
4049
	[newRequest setProxyDomain:[self proxyDomain]];
4050
	[newRequest setProxyHost:[self proxyHost]];
4051
	[newRequest setProxyPort:[self proxyPort]];
4052
	[newRequest setProxyType:[self proxyType]];
4053
	[newRequest setUploadProgressDelegate:[self uploadProgressDelegate]];
4054
	[newRequest setDownloadProgressDelegate:[self downloadProgressDelegate]];
4055
	[newRequest setShouldPresentAuthenticationDialog:[self shouldPresentAuthenticationDialog]];
4056
	[newRequest setShouldPresentProxyAuthenticationDialog:[self shouldPresentProxyAuthenticationDialog]];
4057
	[newRequest setPostLength:[self postLength]];
4058
	[newRequest setHaveBuiltPostBody:[self haveBuiltPostBody]];
4059
	[newRequest setDidStartSelector:[self didStartSelector]];
4060
	[newRequest setDidFinishSelector:[self didFinishSelector]];
4061
	[newRequest setDidFailSelector:[self didFailSelector]];
4062
	[newRequest setTimeOutSeconds:[self timeOutSeconds]];
4063
	[newRequest setShouldResetDownloadProgress:[self shouldResetDownloadProgress]];
4064
	[newRequest setShouldResetUploadProgress:[self shouldResetUploadProgress]];
4065
	[newRequest setShowAccurateProgress:[self showAccurateProgress]];
4066
	[newRequest setDefaultResponseEncoding:[self defaultResponseEncoding]];
4067
	[newRequest setAllowResumeForFileDownloads:[self allowResumeForFileDownloads]];
4068
	[newRequest setUserInfo:[[[self userInfo] copyWithZone:zone] autorelease]];
4069
	[newRequest setTag:[self tag]];
4070
	[newRequest setUseHTTPVersionOne:[self useHTTPVersionOne]];
4071
	[newRequest setShouldRedirect:[self shouldRedirect]];
4072
	[newRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
4073
    [newRequest setClientCertificateIdentity:clientCertificateIdentity];
4074
	[newRequest setClientCertificates:[[clientCertificates copy] autorelease]];
4075
	[newRequest setPACurl:[self PACurl]];
4076
	[newRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
4077
	[newRequest setNumberOfTimesToRetryOnTimeout:[self numberOfTimesToRetryOnTimeout]];
4078
	[newRequest setShouldUseRFC2616RedirectBehaviour:[self shouldUseRFC2616RedirectBehaviour]];
4079
	[newRequest setShouldAttemptPersistentConnection:[self shouldAttemptPersistentConnection]];
4080
	[newRequest setPersistentConnectionTimeoutSeconds:[self persistentConnectionTimeoutSeconds]];
4081
	return newRequest;
4082
}
4083

    
4084
#pragma mark default time out
4085

    
4086
+ (NSTimeInterval)defaultTimeOutSeconds
4087
{
4088
	return defaultTimeOutSeconds;
4089
}
4090

    
4091
+ (void)setDefaultTimeOutSeconds:(NSTimeInterval)newTimeOutSeconds
4092
{
4093
	defaultTimeOutSeconds = newTimeOutSeconds;
4094
}
4095

    
4096

    
4097
#pragma mark client certificate
4098

    
4099
- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity {
4100
    if(clientCertificateIdentity) {
4101
        CFRelease(clientCertificateIdentity);
4102
    }
4103
    
4104
    clientCertificateIdentity = anIdentity;
4105
    
4106
	if (clientCertificateIdentity) {
4107
		CFRetain(clientCertificateIdentity);
4108
	}
4109
}
4110

    
4111

    
4112
#pragma mark session credentials
4113

    
4114
+ (NSMutableArray *)sessionProxyCredentialsStore
4115
{
4116
	[sessionCredentialsLock lock];
4117
	if (!sessionProxyCredentialsStore) {
4118
		sessionProxyCredentialsStore = [[NSMutableArray alloc] init];
4119
	}
4120
	[sessionCredentialsLock unlock];
4121
	return sessionProxyCredentialsStore;
4122
}
4123

    
4124
+ (NSMutableArray *)sessionCredentialsStore
4125
{
4126
	[sessionCredentialsLock lock];
4127
	if (!sessionCredentialsStore) {
4128
		sessionCredentialsStore = [[NSMutableArray alloc] init];
4129
	}
4130
	[sessionCredentialsLock unlock];
4131
	return sessionCredentialsStore;
4132
}
4133

    
4134
+ (void)storeProxyAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
4135
{
4136
	[sessionCredentialsLock lock];
4137
	[self removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
4138
	[[[self class] sessionProxyCredentialsStore] addObject:credentials];
4139
	[sessionCredentialsLock unlock];
4140
}
4141

    
4142
+ (void)storeAuthenticationCredentialsInSessionStore:(NSDictionary *)credentials
4143
{
4144
	[sessionCredentialsLock lock];
4145
	[self removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
4146
	[[[self class] sessionCredentialsStore] addObject:credentials];
4147
	[sessionCredentialsLock unlock];
4148
}
4149

    
4150
+ (void)removeProxyAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
4151
{
4152
	[sessionCredentialsLock lock];
4153
	NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
4154
	NSUInteger i;
4155
	for (i=0; i<[sessionCredentialsList count]; i++) {
4156
		NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
4157
		if ([theCredentials objectForKey:@"Credentials"] == credentials) {
4158
			[sessionCredentialsList removeObjectAtIndex:i];
4159
			[sessionCredentialsLock unlock];
4160
			return;
4161
		}
4162
	}
4163
	[sessionCredentialsLock unlock];
4164
}
4165

    
4166
+ (void)removeAuthenticationCredentialsFromSessionStore:(NSDictionary *)credentials
4167
{
4168
	[sessionCredentialsLock lock];
4169
	NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
4170
	NSUInteger i;
4171
	for (i=0; i<[sessionCredentialsList count]; i++) {
4172
		NSDictionary *theCredentials = [sessionCredentialsList objectAtIndex:i];
4173
		if ([theCredentials objectForKey:@"Credentials"] == credentials) {
4174
			[sessionCredentialsList removeObjectAtIndex:i];
4175
			[sessionCredentialsLock unlock];
4176
			return;
4177
		}
4178
	}
4179
	[sessionCredentialsLock unlock];
4180
}
4181

    
4182
- (NSDictionary *)findSessionProxyAuthenticationCredentials
4183
{
4184
	[sessionCredentialsLock lock];
4185
	NSMutableArray *sessionCredentialsList = [[self class] sessionProxyCredentialsStore];
4186
	for (NSDictionary *theCredentials in sessionCredentialsList) {
4187
		if ([[theCredentials objectForKey:@"Host"] isEqualToString:[self proxyHost]] && [[theCredentials objectForKey:@"Port"] intValue] == [self proxyPort]) {
4188
			[sessionCredentialsLock unlock];
4189
			return theCredentials;
4190
		}
4191
	}
4192
	[sessionCredentialsLock unlock];
4193
	return nil;
4194
}
4195

    
4196

    
4197
- (NSDictionary *)findSessionAuthenticationCredentials
4198
{
4199
	[sessionCredentialsLock lock];
4200
	NSMutableArray *sessionCredentialsList = [[self class] sessionCredentialsStore];
4201
	NSURL *requestURL = [self url];
4202

    
4203
	BOOL haveFoundExactMatch;
4204
	NSDictionary *closeMatch = nil;
4205

    
4206
	// Loop through all the cached credentials we have, looking for the best match for this request
4207
	for (NSDictionary *theCredentials in sessionCredentialsList) {
4208
		
4209
		haveFoundExactMatch = NO;
4210
		NSURL *cachedCredentialsURL = [theCredentials objectForKey:@"URL"];
4211

    
4212
		// Find an exact match (same url)
4213
		if ([cachedCredentialsURL isEqual:[self url]]) {
4214
			haveFoundExactMatch = YES;
4215

    
4216
		// This is not an exact match for the url, and we already have a close match we can use
4217
		} else if (closeMatch) {
4218
			continue;
4219

    
4220
		// Find a close match (same host, scheme and port)
4221
		} else if ([[cachedCredentialsURL host] isEqualToString:[requestURL host]] && ([cachedCredentialsURL port] == [requestURL port] || ([requestURL port] && [[cachedCredentialsURL port] isEqualToNumber:[requestURL port]])) && [[cachedCredentialsURL scheme] isEqualToString:[requestURL scheme]]) {
4222
		} else {
4223
			continue;
4224
		}
4225

    
4226
		// 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
4227
		if ([self authenticationRealm] && ([theCredentials objectForKey:@"AuthenticationRealm"] && ![[theCredentials objectForKey:@"AuthenticationRealm"] isEqualToString:[self authenticationRealm]])) {
4228
			continue;
4229
		}
4230

    
4231
		// If we have a username and password set on the request, check that they are the same as the cached ones
4232
		if ([self username] && [self password]) {
4233
			NSDictionary *usernameAndPassword = [theCredentials objectForKey:@"Credentials"];
4234
			NSString *storedUsername = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername];
4235
			NSString *storedPassword = [usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername];
4236
			if (![storedUsername isEqualToString:[self username]] || ![storedPassword isEqualToString:[self password]]) {
4237
				continue;
4238
			}
4239
		}
4240

    
4241
		// If we have an exact match for the url, use those credentials
4242
		if (haveFoundExactMatch) {
4243
			[sessionCredentialsLock unlock];
4244
			return theCredentials;
4245
		}
4246

    
4247
		// 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
4248
		closeMatch = theCredentials;
4249
	}
4250
	[sessionCredentialsLock unlock];
4251

    
4252
	// Return credentials that matched on host, port and scheme, or nil if we didn't find any
4253
	return closeMatch;
4254
}
4255

    
4256
#pragma mark keychain storage
4257

    
4258
+ (void)saveCredentials:(NSURLCredential *)credentials forHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4259
{
4260
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4261
	[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
4262
}
4263

    
4264
+ (void)saveCredentials:(NSURLCredential *)credentials forProxy:(NSString *)host port:(int)port realm:(NSString *)realm
4265
{
4266
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4267
	[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credentials forProtectionSpace:protectionSpace];
4268
}
4269

    
4270
+ (NSURLCredential *)savedCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4271
{
4272
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4273
	return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4274
}
4275

    
4276
+ (NSURLCredential *)savedCredentialsForProxy:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4277
{
4278
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4279
	return [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4280
}
4281

    
4282
+ (void)removeCredentialsForHost:(NSString *)host port:(int)port protocol:(NSString *)protocol realm:(NSString *)realm
4283
{
4284
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4285
	NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4286
	if (credential) {
4287
		[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace];
4288
	}
4289
}
4290

    
4291
+ (void)removeCredentialsForProxy:(NSString *)host port:(int)port realm:(NSString *)realm
4292
{
4293
	NSURLProtectionSpace *protectionSpace = [[[NSURLProtectionSpace alloc] initWithProxyHost:host port:port type:NSURLProtectionSpaceHTTPProxy realm:realm authenticationMethod:NSURLAuthenticationMethodDefault] autorelease];
4294
	NSURLCredential *credential = [[NSURLCredentialStorage sharedCredentialStorage] defaultCredentialForProtectionSpace:protectionSpace];
4295
	if (credential) {
4296
		[[NSURLCredentialStorage sharedCredentialStorage] removeCredential:credential forProtectionSpace:protectionSpace];
4297
	}
4298
}
4299

    
4300
+ (NSMutableArray *)sessionCookies
4301
{
4302
	[sessionCookiesLock lock];
4303
	if (!sessionCookies) {
4304
		[ASIHTTPRequest setSessionCookies:[NSMutableArray array]];
4305
	}
4306
	NSMutableArray *cookies = [[sessionCookies retain] autorelease];
4307
	[sessionCookiesLock unlock];
4308
	return cookies;
4309
}
4310

    
4311
+ (void)setSessionCookies:(NSMutableArray *)newSessionCookies
4312
{
4313
	[sessionCookiesLock lock];
4314
	// Remove existing cookies from the persistent store
4315
	for (NSHTTPCookie *cookie in sessionCookies) {
4316
		[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
4317
	}
4318
	[sessionCookies release];
4319
	sessionCookies = [newSessionCookies retain];
4320
	[sessionCookiesLock unlock];
4321
}
4322

    
4323
+ (void)addSessionCookie:(NSHTTPCookie *)newCookie
4324
{
4325
	[sessionCookiesLock lock];
4326
	NSHTTPCookie *cookie;
4327
	NSUInteger i;
4328
	NSUInteger max = [[ASIHTTPRequest sessionCookies] count];
4329
	for (i=0; i<max; i++) {
4330
		cookie = [[ASIHTTPRequest sessionCookies] objectAtIndex:i];
4331
		if ([[cookie domain] isEqualToString:[newCookie domain]] && [[cookie path] isEqualToString:[newCookie path]] && [[cookie name] isEqualToString:[newCookie name]]) {
4332
			[[ASIHTTPRequest sessionCookies] removeObjectAtIndex:i];
4333
			break;
4334
		}
4335
	}
4336
	[[ASIHTTPRequest sessionCookies] addObject:newCookie];
4337
	[sessionCookiesLock unlock];
4338
}
4339

    
4340
// Dump all session data (authentication and cookies)
4341
+ (void)clearSession
4342
{
4343
	[sessionCredentialsLock lock];
4344
	[[[self class] sessionCredentialsStore] removeAllObjects];
4345
	[sessionCredentialsLock unlock];
4346
	[[self class] setSessionCookies:nil];
4347
	[[[self class] defaultCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
4348
}
4349

    
4350
#pragma mark get user agent
4351

    
4352
+ (NSString *)defaultUserAgentString
4353
{
4354
	@synchronized (self) {
4355

    
4356
		if (!defaultUserAgent) {
4357

    
4358
			NSBundle *bundle = [NSBundle bundleForClass:[self class]];
4359

    
4360
			// Attempt to find a name for this application
4361
			NSString *appName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
4362
			if (!appName) {
4363
				appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"];
4364
			}
4365

    
4366
			NSData *latin1Data = [appName dataUsingEncoding:NSUTF8StringEncoding];
4367
			appName = [[[NSString alloc] initWithData:latin1Data encoding:NSISOLatin1StringEncoding] autorelease];
4368

    
4369
			// If we couldn't find one, we'll give up (and ASIHTTPRequest will use the standard CFNetwork user agent)
4370
			if (!appName) {
4371
				return nil;
4372
			}
4373

    
4374
			NSString *appVersion = nil;
4375
			NSString *marketingVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
4376
			NSString *developmentVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
4377
			if (marketingVersionNumber && developmentVersionNumber) {
4378
				if ([marketingVersionNumber isEqualToString:developmentVersionNumber]) {
4379
					appVersion = marketingVersionNumber;
4380
				} else {
4381
					appVersion = [NSString stringWithFormat:@"%@ rv:%@",marketingVersionNumber,developmentVersionNumber];
4382
				}
4383
			} else {
4384
				appVersion = (marketingVersionNumber ? marketingVersionNumber : developmentVersionNumber);
4385
			}
4386

    
4387
			NSString *deviceName;
4388
			NSString *OSName;
4389
			NSString *OSVersion;
4390
			NSString *locale = [[NSLocale currentLocale] localeIdentifier];
4391

    
4392
			#if TARGET_OS_IPHONE
4393
				UIDevice *device = [UIDevice currentDevice];
4394
				deviceName = [device model];
4395
				OSName = [device systemName];
4396
				OSVersion = [device systemVersion];
4397

    
4398
			#else
4399
				deviceName = @"Macintosh";
4400
				OSName = @"Mac OS X";
4401

    
4402
				// From http://www.cocoadev.com/index.pl?DeterminingOSVersion
4403
				// We won't bother to check for systems prior to 10.4, since ASIHTTPRequest only works on 10.5+
4404
				OSErr err;
4405
				SInt32 versionMajor, versionMinor, versionBugFix;
4406
				err = Gestalt(gestaltSystemVersionMajor, &versionMajor);
4407
				if (err != noErr) return nil;
4408
				err = Gestalt(gestaltSystemVersionMinor, &versionMinor);
4409
				if (err != noErr) return nil;
4410
				err = Gestalt(gestaltSystemVersionBugFix, &versionBugFix);
4411
				if (err != noErr) return nil;
4412
				OSVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix];
4413
			#endif
4414

    
4415
			// Takes the form "My Application 1.0 (Macintosh; Mac OS X 10.5.7; en_GB)"
4416
			[self setDefaultUserAgentString:[NSString stringWithFormat:@"%@ %@ (%@; %@ %@; %@)", appName, appVersion, deviceName, OSName, OSVersion, locale]];	
4417
		}
4418
		return [[defaultUserAgent retain] autorelease];
4419
	}
4420
}
4421

    
4422
+ (void)setDefaultUserAgentString:(NSString *)agent
4423
{
4424
	@synchronized (self) {
4425
		if (defaultUserAgent == agent) {
4426
			return;
4427
		}
4428
		[defaultUserAgent release];
4429
		defaultUserAgent = [agent copy];
4430
	}
4431
}
4432

    
4433

    
4434
#pragma mark mime-type detection
4435

    
4436
+ (NSString *)mimeTypeForFileAtPath:(NSString *)path
4437
{
4438
	if (![[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:path]) {
4439
		return nil;
4440
	}
4441
	// Borrowed from http://stackoverflow.com/questions/2439020/wheres-the-iphone-mime-type-database
4442
	CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[path pathExtension], NULL);
4443
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
4444
    CFRelease(UTI);
4445
	if (!MIMEType) {
4446
		return @"application/octet-stream";
4447
	}
4448
    return NSMakeCollectable([(NSString *)MIMEType autorelease]);
4449
}
4450

    
4451
#pragma mark bandwidth measurement / throttling
4452

    
4453
- (void)performThrottling
4454
{
4455
	if (![self readStream]) {
4456
		return;
4457
	}
4458
	[ASIHTTPRequest measureBandwidthUsage];
4459
	if ([ASIHTTPRequest isBandwidthThrottled]) {
4460
		[bandwidthThrottlingLock lock];
4461
		// Handle throttling
4462
		if (throttleWakeUpTime) {
4463
			if ([throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] > 0) {
4464
				if ([self readStreamIsScheduled]) {
4465
					[self unscheduleReadStream];
4466
					#if DEBUG_THROTTLING
4467
					NSLog(@"[THROTTLING] Sleeping request %@ until after %@",self,throttleWakeUpTime);
4468
					#endif
4469
				}
4470
			} else {
4471
				if (![self readStreamIsScheduled]) {
4472
					[self scheduleReadStream];
4473
					#if DEBUG_THROTTLING
4474
					NSLog(@"[THROTTLING] Waking up request %@",self);
4475
					#endif
4476
				}
4477
			}
4478
		} 
4479
		[bandwidthThrottlingLock unlock];
4480
		
4481
	// Bandwidth throttling must have been turned off since we last looked, let's re-schedule the stream
4482
	} else if (![self readStreamIsScheduled]) {
4483
		[self scheduleReadStream];			
4484
	}
4485
}
4486

    
4487
+ (BOOL)isBandwidthThrottled
4488
{
4489
#if TARGET_OS_IPHONE
4490
	[bandwidthThrottlingLock lock];
4491

    
4492
	BOOL throttle = isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond > 0));
4493
	[bandwidthThrottlingLock unlock];
4494
	return throttle;
4495
#else
4496
	[bandwidthThrottlingLock lock];
4497
	BOOL throttle = (maxBandwidthPerSecond > 0);
4498
	[bandwidthThrottlingLock unlock];
4499
	return throttle;
4500
#endif
4501
}
4502

    
4503
+ (unsigned long)maxBandwidthPerSecond
4504
{
4505
	[bandwidthThrottlingLock lock];
4506
	unsigned long amount = maxBandwidthPerSecond;
4507
	[bandwidthThrottlingLock unlock];
4508
	return amount;
4509
}
4510

    
4511
+ (void)setMaxBandwidthPerSecond:(unsigned long)bytes
4512
{
4513
	[bandwidthThrottlingLock lock];
4514
	maxBandwidthPerSecond = bytes;
4515
	[bandwidthThrottlingLock unlock];
4516
}
4517

    
4518
+ (void)incrementBandwidthUsedInLastSecond:(unsigned long)bytes
4519
{
4520
	[bandwidthThrottlingLock lock];
4521
	bandwidthUsedInLastSecond += bytes;
4522
	[bandwidthThrottlingLock unlock];
4523
}
4524

    
4525
+ (void)recordBandwidthUsage
4526
{
4527
	if (bandwidthUsedInLastSecond == 0) {
4528
		[bandwidthUsageTracker removeAllObjects];
4529
	} else {
4530
		NSTimeInterval interval = [bandwidthMeasurementDate timeIntervalSinceNow];
4531
		while ((interval < 0 || [bandwidthUsageTracker count] > 5) && [bandwidthUsageTracker count] > 0) {
4532
			[bandwidthUsageTracker removeObjectAtIndex:0];
4533
			interval++;
4534
		}
4535
	}
4536
	#if DEBUG_THROTTLING
4537
	NSLog(@"[THROTTLING] ===Used: %u bytes of bandwidth in last measurement period===",bandwidthUsedInLastSecond);
4538
	#endif
4539
	[bandwidthUsageTracker addObject:[NSNumber numberWithUnsignedLong:bandwidthUsedInLastSecond]];
4540
	[bandwidthMeasurementDate release];
4541
	bandwidthMeasurementDate = [[NSDate dateWithTimeIntervalSinceNow:1] retain];
4542
	bandwidthUsedInLastSecond = 0;
4543
	
4544
	NSUInteger measurements = [bandwidthUsageTracker count];
4545
	unsigned long totalBytes = 0;
4546
	for (NSNumber *bytes in bandwidthUsageTracker) {
4547
		totalBytes += [bytes unsignedLongValue];
4548
	}
4549
	averageBandwidthUsedPerSecond = totalBytes/measurements;		
4550
}
4551

    
4552
+ (unsigned long)averageBandwidthUsedPerSecond
4553
{
4554
	[bandwidthThrottlingLock lock];
4555
	unsigned long amount = 	averageBandwidthUsedPerSecond;
4556
	[bandwidthThrottlingLock unlock];
4557
	return amount;
4558
}
4559

    
4560
+ (void)measureBandwidthUsage
4561
{
4562
	// 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
4563
	[bandwidthThrottlingLock lock];
4564

    
4565
	if (!bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
4566
		[ASIHTTPRequest recordBandwidthUsage];
4567
	}
4568
	
4569
	// Are we performing bandwidth throttling?
4570
	if (
4571
	#if TARGET_OS_IPHONE
4572
	isBandwidthThrottled || (!shouldThrottleBandwithForWWANOnly && (maxBandwidthPerSecond))
4573
	#else
4574
	maxBandwidthPerSecond
4575
	#endif
4576
	) {
4577
		// How much data can we still send or receive this second?
4578
		long long bytesRemaining = (long long)maxBandwidthPerSecond - (long long)bandwidthUsedInLastSecond;
4579
			
4580
		// Have we used up our allowance?
4581
		if (bytesRemaining < 0) {
4582
			
4583
			// 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)
4584
			double extraSleepyTime = (-bytesRemaining/(maxBandwidthPerSecond*1.0));
4585
			[throttleWakeUpTime release];
4586
			throttleWakeUpTime = [[NSDate alloc] initWithTimeInterval:extraSleepyTime sinceDate:bandwidthMeasurementDate];
4587
		}
4588
	}
4589
	[bandwidthThrottlingLock unlock];
4590
}
4591
	
4592
+ (unsigned long)maxUploadReadLength
4593
{
4594
	[bandwidthThrottlingLock lock];
4595
	
4596
	// 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
4597
	long long toRead = maxBandwidthPerSecond/4;
4598
	if (maxBandwidthPerSecond > 0 && (bandwidthUsedInLastSecond + toRead > maxBandwidthPerSecond)) {
4599
		toRead = (long long)maxBandwidthPerSecond-(long long)bandwidthUsedInLastSecond;
4600
		if (toRead < 0) {
4601
			toRead = 0;
4602
		}
4603
	}
4604
	
4605
	if (toRead == 0 || !bandwidthMeasurementDate || [bandwidthMeasurementDate timeIntervalSinceNow] < -0) {
4606
		[throttleWakeUpTime release];
4607
		throttleWakeUpTime = [bandwidthMeasurementDate retain];
4608
	}
4609
	[bandwidthThrottlingLock unlock];	
4610
	return (unsigned long)toRead;
4611
}
4612
	
4613

    
4614
#if TARGET_OS_IPHONE
4615
+ (void)setShouldThrottleBandwidthForWWAN:(BOOL)throttle
4616
{
4617
	if (throttle) {
4618
		[ASIHTTPRequest throttleBandwidthForWWANUsingLimit:ASIWWANBandwidthThrottleAmount];
4619
	} else {
4620
		[ASIHTTPRequest unsubscribeFromNetworkReachabilityNotifications];
4621
		[ASIHTTPRequest setMaxBandwidthPerSecond:0];
4622
		[bandwidthThrottlingLock lock];
4623
		isBandwidthThrottled = NO;
4624
		shouldThrottleBandwithForWWANOnly = NO;
4625
		[bandwidthThrottlingLock unlock];
4626
	}
4627
}
4628

    
4629
+ (void)throttleBandwidthForWWANUsingLimit:(unsigned long)limit
4630
{	
4631
	[bandwidthThrottlingLock lock];
4632
	shouldThrottleBandwithForWWANOnly = YES;
4633
	maxBandwidthPerSecond = limit;
4634
	[ASIHTTPRequest registerForNetworkReachabilityNotifications];	
4635
	[bandwidthThrottlingLock unlock];
4636
	[ASIHTTPRequest reachabilityChanged:nil];
4637
}
4638

    
4639
#pragma mark reachability
4640

    
4641
+ (void)registerForNetworkReachabilityNotifications
4642
{
4643
	[[Reachability reachabilityForInternetConnection] startNotifier];
4644
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
4645
}
4646

    
4647

    
4648
+ (void)unsubscribeFromNetworkReachabilityNotifications
4649
{
4650
	[[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
4651
}
4652

    
4653
+ (BOOL)isNetworkReachableViaWWAN
4654
{
4655
	return ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == ReachableViaWWAN);	
4656
}
4657

    
4658
+ (void)reachabilityChanged:(NSNotification *)note
4659
{
4660
	[bandwidthThrottlingLock lock];
4661
	isBandwidthThrottled = [ASIHTTPRequest isNetworkReachableViaWWAN];
4662
	[bandwidthThrottlingLock unlock];
4663
}
4664
#endif
4665

    
4666
#pragma mark queue
4667

    
4668
// Returns the shared queue
4669
+ (NSOperationQueue *)sharedQueue
4670
{
4671
    return [[sharedQueue retain] autorelease];
4672
}
4673

    
4674
#pragma mark cache
4675

    
4676
+ (void)setDefaultCache:(id <ASICacheDelegate>)cache
4677
{
4678
	@synchronized (self) {
4679
		[cache retain];
4680
		[defaultCache release];
4681
		defaultCache = cache;
4682
	}
4683
}
4684

    
4685
+ (id <ASICacheDelegate>)defaultCache
4686
{
4687
    @synchronized(self) {
4688
        return [[defaultCache retain] autorelease];
4689
    }
4690
}
4691

    
4692

    
4693
#pragma mark network activity
4694

    
4695
+ (BOOL)isNetworkInUse
4696
{
4697
	[connectionsLock lock];
4698
	BOOL inUse = (runningRequestCount > 0);
4699
	[connectionsLock unlock];
4700
	return inUse;
4701
}
4702

    
4703
+ (void)setShouldUpdateNetworkActivityIndicator:(BOOL)shouldUpdate
4704
{
4705
	[connectionsLock lock];
4706
	shouldUpdateNetworkActivityIndicator = shouldUpdate;
4707
	[connectionsLock unlock];
4708
}
4709

    
4710
+ (void)showNetworkActivityIndicator
4711
{
4712
#if TARGET_OS_IPHONE
4713
	[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
4714
#endif
4715
}
4716

    
4717
+ (void)hideNetworkActivityIndicator
4718
{
4719
#if TARGET_OS_IPHONE
4720
	[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];	
4721
#endif
4722
}
4723

    
4724

    
4725
/* Always called on main thread */
4726
+ (void)hideNetworkActivityIndicatorAfterDelay
4727
{
4728
	[self performSelector:@selector(hideNetworkActivityIndicatorIfNeeeded) withObject:nil afterDelay:0.5];
4729
}
4730

    
4731
+ (void)hideNetworkActivityIndicatorIfNeeeded
4732
{
4733
	[connectionsLock lock];
4734
	if (runningRequestCount == 0) {
4735
		[self hideNetworkActivityIndicator];
4736
	}
4737
	[connectionsLock unlock];
4738
}
4739

    
4740

    
4741
#pragma mark threading behaviour
4742

    
4743
// In the default implementation, all requests run in a single background thread
4744
// Advanced users only: Override this method in a subclass for a different threading behaviour
4745
// Eg: return [NSThread mainThread] to run all requests in the main thread
4746
// Alternatively, you can create a thread on demand, or manage a pool of threads
4747
// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun())
4748
// Requests will stop the runloop when they complete
4749
// If you have multiple requests sharing the thread or you want to re-use the thread, you'll need to restart the runloop
4750
+ (NSThread *)threadForRequest:(ASIHTTPRequest *)request
4751
{
4752
	if (networkThread == nil) {
4753
		@synchronized(self) {
4754
			if (networkThread == nil) {
4755
				networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil];
4756
				[networkThread start];
4757
			}
4758
		}
4759
	}
4760
	return networkThread;
4761
}
4762

    
4763
+ (void)runRequests
4764
{
4765
	// Should keep the runloop from exiting
4766
	CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4767
	CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
4768
	CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
4769

    
4770
    BOOL runAlways = YES; // Introduced to cheat Static Analyzer
4771
	while (runAlways) {
4772
		NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
4773
		CFRunLoopRun();
4774
		[pool release];
4775
	}
4776

    
4777
	// Should never be called, but anyway
4778
	CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
4779
	CFRelease(source);
4780
}
4781

    
4782
#pragma mark miscellany 
4783

    
4784
#if TARGET_OS_IPHONE
4785
+ (BOOL)isMultitaskingSupported
4786
{
4787
	BOOL multiTaskingSupported = NO;
4788
	if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)]) {
4789
		multiTaskingSupported = [(id)[UIDevice currentDevice] isMultitaskingSupported];
4790
	}
4791
	return multiTaskingSupported;
4792
}
4793
#endif
4794

    
4795
// From: http://www.cocoadev.com/index.pl?BaseSixtyFour
4796

    
4797
+ (NSString*)base64forData:(NSData*)theData {
4798
	
4799
	const uint8_t* input = (const uint8_t*)[theData bytes];
4800
	NSInteger length = [theData length];
4801
	
4802
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
4803
	
4804
    NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
4805
    uint8_t* output = (uint8_t*)data.mutableBytes;
4806
	
4807
	NSInteger i,i2;
4808
    for (i=0; i < length; i += 3) {
4809
        NSInteger value = 0;
4810
		for (i2=0; i2<3; i2++) {
4811
            value <<= 8;
4812
            if (i+i2 < length) {
4813
                value |= (0xFF & input[i+i2]);
4814
            }
4815
        }
4816
		
4817
        NSInteger theIndex = (i / 3) * 4;
4818
        output[theIndex + 0] =                    table[(value >> 18) & 0x3F];
4819
        output[theIndex + 1] =                    table[(value >> 12) & 0x3F];
4820
        output[theIndex + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';
4821
        output[theIndex + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';
4822
    }
4823
	
4824
    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
4825
}
4826

    
4827
// Based on hints from http://stackoverflow.com/questions/1850824/parsing-a-rfc-822-date-with-nsdateformatter
4828
+ (NSDate *)dateFromRFC1123String:(NSString *)string
4829
{
4830
	NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
4831
	[formatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
4832
	// Does the string include a week day?
4833
	NSString *day = @"";
4834
	if ([string rangeOfString:@","].location != NSNotFound) {
4835
		day = @"EEE, ";
4836
	}
4837
	// Does the string include seconds?
4838
	NSString *seconds = @"";
4839
	if ([[string componentsSeparatedByString:@":"] count] == 3) {
4840
		seconds = @":ss";
4841
	}
4842
	[formatter setDateFormat:[NSString stringWithFormat:@"%@dd MMM yyyy HH:mm%@ z",day,seconds]];
4843
	return [formatter dateFromString:string];
4844
}
4845

    
4846
+ (void)parseMimeType:(NSString **)mimeType andResponseEncoding:(NSStringEncoding *)stringEncoding fromContentType:(NSString *)contentType
4847
{
4848
	if (!contentType) {
4849
		return;
4850
	}
4851
	NSScanner *charsetScanner = [NSScanner scannerWithString: contentType];
4852
	if (![charsetScanner scanUpToString:@";" intoString:mimeType] || [charsetScanner scanLocation] == [contentType length]) {
4853
		*mimeType = [contentType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
4854
		return;
4855
	}
4856
	*mimeType = [*mimeType stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
4857
	NSString *charsetSeparator = @"charset=";
4858
	NSString *IANAEncoding = nil;
4859

    
4860
	if ([charsetScanner scanUpToString: charsetSeparator intoString: NULL] && [charsetScanner scanLocation] < [contentType length]) {
4861
		[charsetScanner setScanLocation: [charsetScanner scanLocation] + [charsetSeparator length]];
4862
		[charsetScanner scanUpToString: @";" intoString: &IANAEncoding];
4863
	}
4864

    
4865
	if (IANAEncoding) {
4866
		CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)IANAEncoding);
4867
		if (cfEncoding != kCFStringEncodingInvalidId) {
4868
			*stringEncoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
4869
		}
4870
	}
4871
}
4872

    
4873
#pragma mark -
4874
#pragma mark blocks
4875
#if NS_BLOCKS_AVAILABLE
4876
- (void)setStartedBlock:(ASIBasicBlock)aStartedBlock
4877
{
4878
	[startedBlock release];
4879
	startedBlock = [aStartedBlock copy];
4880
}
4881

    
4882
- (void)setHeadersReceivedBlock:(ASIHeadersBlock)aReceivedBlock
4883
{
4884
	[headersReceivedBlock release];
4885
	headersReceivedBlock = [aReceivedBlock copy];
4886
}
4887

    
4888
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock
4889
{
4890
	[completionBlock release];
4891
	completionBlock = [aCompletionBlock copy];
4892
}
4893

    
4894
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock
4895
{
4896
	[failureBlock release];
4897
	failureBlock = [aFailedBlock copy];
4898
}
4899

    
4900
- (void)setBytesReceivedBlock:(ASIProgressBlock)aBytesReceivedBlock
4901
{
4902
	[bytesReceivedBlock release];
4903
	bytesReceivedBlock = [aBytesReceivedBlock copy];
4904
}
4905

    
4906
- (void)setBytesSentBlock:(ASIProgressBlock)aBytesSentBlock
4907
{
4908
	[bytesSentBlock release];
4909
	bytesSentBlock = [aBytesSentBlock copy];
4910
}
4911

    
4912
- (void)setDownloadSizeIncrementedBlock:(ASISizeBlock)aDownloadSizeIncrementedBlock{
4913
	[downloadSizeIncrementedBlock release];
4914
	downloadSizeIncrementedBlock = [aDownloadSizeIncrementedBlock copy];
4915
}
4916

    
4917
- (void)setUploadSizeIncrementedBlock:(ASISizeBlock)anUploadSizeIncrementedBlock
4918
{
4919
	[uploadSizeIncrementedBlock release];
4920
	uploadSizeIncrementedBlock = [anUploadSizeIncrementedBlock copy];
4921
}
4922

    
4923
- (void)setDataReceivedBlock:(ASIDataBlock)aReceivedBlock
4924
{
4925
	[dataReceivedBlock release];
4926
	dataReceivedBlock = [aReceivedBlock copy];
4927
}
4928

    
4929
- (void)setAuthenticationNeededBlock:(ASIBasicBlock)anAuthenticationBlock
4930
{
4931
	[authenticationNeededBlock release];
4932
	authenticationNeededBlock = [anAuthenticationBlock copy];
4933
}
4934
- (void)setProxyAuthenticationNeededBlock:(ASIBasicBlock)aProxyAuthenticationBlock
4935
{
4936
	[proxyAuthenticationNeededBlock release];
4937
	proxyAuthenticationNeededBlock = [aProxyAuthenticationBlock copy];
4938
}
4939
- (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock
4940
{
4941
	[requestRedirectedBlock release];
4942
	requestRedirectedBlock = [aRedirectBlock copy];
4943
}
4944
#endif
4945

    
4946
#pragma mark ===
4947

    
4948
@synthesize username;
4949
@synthesize password;
4950
@synthesize userAgent;
4951
@synthesize domain;
4952
@synthesize proxyUsername;
4953
@synthesize proxyPassword;
4954
@synthesize proxyDomain;
4955
@synthesize url;
4956
@synthesize originalURL;
4957
@synthesize delegate;
4958
@synthesize queue;
4959
@synthesize uploadProgressDelegate;
4960
@synthesize downloadProgressDelegate;
4961
@synthesize useKeychainPersistence;
4962
@synthesize useSessionPersistence;
4963
@synthesize useCookiePersistence;
4964
@synthesize downloadDestinationPath;
4965
@synthesize temporaryFileDownloadPath;
4966
@synthesize temporaryUncompressedDataDownloadPath;
4967
@synthesize didStartSelector;
4968
@synthesize didReceiveResponseHeadersSelector;
4969
@synthesize willRedirectSelector;
4970
@synthesize didFinishSelector;
4971
@synthesize didFailSelector;
4972
@synthesize didReceiveDataSelector;
4973
@synthesize authenticationRealm;
4974
@synthesize proxyAuthenticationRealm;
4975
@synthesize error;
4976
@synthesize complete;
4977
@synthesize requestHeaders;
4978
@synthesize responseHeaders;
4979
@synthesize responseCookies;
4980
@synthesize requestCookies;
4981
@synthesize requestCredentials;
4982
@synthesize responseStatusCode;
4983
@synthesize rawResponseData;
4984
@synthesize lastActivityTime;
4985
@synthesize timeOutSeconds;
4986
@synthesize requestMethod;
4987
@synthesize postBody;
4988
@synthesize compressedPostBody;
4989
@synthesize contentLength;
4990
@synthesize partialDownloadSize;
4991
@synthesize postLength;
4992
@synthesize shouldResetDownloadProgress;
4993
@synthesize shouldResetUploadProgress;
4994
@synthesize mainRequest;
4995
@synthesize totalBytesRead;
4996
@synthesize totalBytesSent;
4997
@synthesize showAccurateProgress;
4998
@synthesize uploadBufferSize;
4999
@synthesize defaultResponseEncoding;
5000
@synthesize responseEncoding;
5001
@synthesize allowCompressedResponse;
5002
@synthesize allowResumeForFileDownloads;
5003
@synthesize userInfo;
5004
@synthesize tag;
5005
@synthesize postBodyFilePath;
5006
@synthesize compressedPostBodyFilePath;
5007
@synthesize postBodyWriteStream;
5008
@synthesize postBodyReadStream;
5009
@synthesize shouldStreamPostDataFromDisk;
5010
@synthesize didCreateTemporaryPostDataFile;
5011
@synthesize useHTTPVersionOne;
5012
@synthesize lastBytesRead;
5013
@synthesize lastBytesSent;
5014
@synthesize cancelledLock;
5015
@synthesize haveBuiltPostBody;
5016
@synthesize fileDownloadOutputStream;
5017
@synthesize inflatedFileDownloadOutputStream;
5018
@synthesize authenticationRetryCount;
5019
@synthesize proxyAuthenticationRetryCount;
5020
@synthesize updatedProgress;
5021
@synthesize shouldRedirect;
5022
@synthesize validatesSecureCertificate;
5023
@synthesize needsRedirect;
5024
@synthesize redirectCount;
5025
@synthesize shouldCompressRequestBody;
5026
@synthesize proxyCredentials;
5027
@synthesize proxyHost;
5028
@synthesize proxyPort;
5029
@synthesize proxyType;
5030
@synthesize PACurl;
5031
@synthesize authenticationScheme;
5032
@synthesize proxyAuthenticationScheme;
5033
@synthesize shouldPresentAuthenticationDialog;
5034
@synthesize shouldPresentProxyAuthenticationDialog;
5035
@synthesize authenticationNeeded;
5036
@synthesize responseStatusMessage;
5037
@synthesize shouldPresentCredentialsBeforeChallenge;
5038
@synthesize haveBuiltRequestHeaders;
5039
@synthesize inProgress;
5040
@synthesize numberOfTimesToRetryOnTimeout;
5041
@synthesize retryCount;
5042
@synthesize willRetryRequest;
5043
@synthesize shouldAttemptPersistentConnection;
5044
@synthesize persistentConnectionTimeoutSeconds;
5045
@synthesize connectionCanBeReused;
5046
@synthesize connectionInfo;
5047
@synthesize readStream;
5048
@synthesize readStreamIsScheduled;
5049
@synthesize shouldUseRFC2616RedirectBehaviour;
5050
@synthesize downloadComplete;
5051
@synthesize requestID;
5052
@synthesize runLoopMode;
5053
@synthesize statusTimer;
5054
@synthesize downloadCache;
5055
@synthesize cachePolicy;
5056
@synthesize cacheStoragePolicy;
5057
@synthesize didUseCachedResponse;
5058
@synthesize secondsToCache;
5059
@synthesize clientCertificates;
5060
@synthesize redirectURL;
5061
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
5062
@synthesize shouldContinueWhenAppEntersBackground;
5063
#endif
5064
@synthesize dataDecompressor;
5065
@synthesize shouldWaitToInflateCompressedResponses;
5066

    
5067
@synthesize isPACFileRequest;
5068
@synthesize PACFileRequest;
5069
@synthesize PACFileReadStream;
5070
@synthesize PACFileData;
5071

    
5072
@synthesize isSynchronous;
5073
@end