PageRenderTime 37ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/IOSBoilerplate/ASIHTTPRequest.m

https://github.com/HueMachine/iOS-boilerplate
Objective C | 1416 lines | 1038 code | 247 blank | 131 comment | 203 complexity | f4539c265198479a5041191549256d98 MD5 | raw file
  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. #import "ASIHTTPRequest.h"
  13. #if TARGET_OS_IPHONE
  14. #import "Reachability.h"
  15. #import "ASIAuthenticationDialog.h"
  16. #import <MobileCoreServices/MobileCoreServices.h>
  17. #else
  18. #import <SystemConfiguration/SystemConfiguration.h>
  19. #endif
  20. #import "ASIInputStream.h"
  21. #import "ASIDataDecompressor.h"
  22. #import "ASIDataCompressor.h"
  23. // Automatically set on build
  24. NSString *ASIHTTPRequestVersion = @"v1.8.1-33 2011-08-20";
  25. static NSString *defaultUserAgent = nil;
  26. NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
  27. static NSString *ASIHTTPRequestRunLoopMode = @"ASIHTTPRequestRunLoopMode";
  28. static const CFOptionFlags kNetworkEvents = kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred;
  29. // In memory caches of credentials, used on when useSessionPersistence is YES
  30. static NSMutableArray *sessionCredentialsStore = nil;
  31. static NSMutableArray *sessionProxyCredentialsStore = nil;
  32. // This lock mediates access to session credentials
  33. static NSRecursiveLock *sessionCredentialsLock = nil;
  34. // We keep track of cookies we have received here so we can remove them from the sharedHTTPCookieStorage later
  35. static NSMutableArray *sessionCookies = nil;
  36. // The number of times we will allow requests to redirect before we fail with a redirection error
  37. const int RedirectionLimit = 5;
  38. // The default number of seconds to use for a timeout
  39. static NSTimeInterval defaultTimeOutSeconds = 10;
  40. static void ReadStreamClientCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo) {
  41. [((ASIHTTPRequest*)clientCallBackInfo) handleNetworkEvent: type];
  42. }
  43. // This lock prevents the operation from being cancelled while it is trying to update the progress, and vice versa
  44. static NSRecursiveLock *progressLock;
  45. static NSError *ASIRequestCancelledError;
  46. static NSError *ASIRequestTimedOutError;
  47. static NSError *ASIAuthenticationError;
  48. static NSError *ASIUnableToCreateRequestError;
  49. static NSError *ASITooMuchRedirectionError;
  50. static NSMutableArray *bandwidthUsageTracker = nil;
  51. static unsigned long averageBandwidthUsedPerSecond = 0;
  52. // These are used for queuing persistent connections on the same connection
  53. // Incremented every time we specify we want a new connection
  54. static unsigned int nextConnectionNumberToCreate = 0;
  55. // An array of connectionInfo dictionaries.
  56. // 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
  57. static NSMutableArray *persistentConnectionsPool = nil;
  58. // Mediates access to the persistent connections pool
  59. static NSRecursiveLock *connectionsLock = nil;
  60. // Each request gets a new id, we store this rather than a ref to the request itself in the connectionInfo dictionary.
  61. // We do this so we don't have to keep the request around while we wait for the connection to expire
  62. static unsigned int nextRequestID = 0;
  63. // Records how much bandwidth all requests combined have used in the last second
  64. static unsigned long bandwidthUsedInLastSecond = 0;
  65. // A date one second in the future from the time it was created
  66. static NSDate *bandwidthMeasurementDate = nil;
  67. // Since throttling variables are shared among all requests, we'll use a lock to mediate access
  68. static NSLock *bandwidthThrottlingLock = nil;
  69. // the maximum number of bytes that can be transmitted in one second
  70. static unsigned long maxBandwidthPerSecond = 0;
  71. // A default figure for throttling bandwidth on mobile devices
  72. unsigned long const ASIWWANBandwidthThrottleAmount = 14800;
  73. #if TARGET_OS_IPHONE
  74. // YES when bandwidth throttling is active
  75. // This flag does not denote whether throttling is turned on - rather whether it is currently in use
  76. // It will be set to NO when throttling was turned on with setShouldThrottleBandwidthForWWAN, but a WI-FI connection is active
  77. static BOOL isBandwidthThrottled = NO;
  78. // When YES, bandwidth will be automatically throttled when using WWAN (3G/Edge/GPRS)
  79. // Wifi will not be throttled
  80. static BOOL shouldThrottleBandwithForWWANOnly = NO;
  81. #endif
  82. // Mediates access to the session cookies so requests
  83. static NSRecursiveLock *sessionCookiesLock = nil;
  84. // This lock ensures delegates only receive one notification that authentication is required at once
  85. // When using ASIAuthenticationDialogs, it also ensures only one dialog is shown at once
  86. // If a request can't acquire the lock immediately, it means a dialog is being shown or a delegate is handling the authentication challenge
  87. // Once it gets the lock, it will try to look for existing credentials again rather than showing the dialog / notifying the delegate
  88. // This is so it can make use of any credentials supplied for the other request, if they are appropriate
  89. static NSRecursiveLock *delegateAuthenticationLock = nil;
  90. // When throttling bandwidth, Set to a date in future that we will allow all requests to wake up and reschedule their streams
  91. static NSDate *throttleWakeUpTime = nil;
  92. static id <ASICacheDelegate> defaultCache = nil;
  93. // Used for tracking when requests are using the network
  94. static unsigned int runningRequestCount = 0;
  95. // You can use [ASIHTTPRequest setShouldUpdateNetworkActivityIndicator:NO] if you want to manage it yourself
  96. // Alternatively, override showNetworkActivityIndicator / hideNetworkActivityIndicator
  97. // By default this does nothing on Mac OS X, but again override the above methods for a different behaviour
  98. static BOOL shouldUpdateNetworkActivityIndicator = YES;
  99. // The thread all requests will run on
  100. // Hangs around forever, but will be blocked unless there are requests underway
  101. static NSThread *networkThread = nil;
  102. static NSOperationQueue *sharedQueue = nil;
  103. // Private stuff
  104. @interface ASIHTTPRequest ()
  105. - (void)cancelLoad;
  106. - (void)destroyReadStream;
  107. - (void)scheduleReadStream;
  108. - (void)unscheduleReadStream;
  109. - (BOOL)willAskDelegateForCredentials;
  110. - (BOOL)willAskDelegateForProxyCredentials;
  111. - (void)askDelegateForProxyCredentials;
  112. - (void)askDelegateForCredentials;
  113. - (void)failAuthentication;
  114. + (void)measureBandwidthUsage;
  115. + (void)recordBandwidthUsage;
  116. - (void)startRequest;
  117. - (void)updateStatus:(NSTimer *)timer;
  118. - (void)checkRequestStatus;
  119. - (void)reportFailure;
  120. - (void)reportFinished;
  121. - (void)markAsFinished;
  122. - (void)performRedirect;
  123. - (BOOL)shouldTimeOut;
  124. - (BOOL)willRedirect;
  125. - (BOOL)willAskDelegateToConfirmRedirect;
  126. + (void)performInvocation:(NSInvocation *)invocation onTarget:(id *)target releasingObject:(id)objectToRelease;
  127. + (void)hideNetworkActivityIndicatorAfterDelay;
  128. + (void)hideNetworkActivityIndicatorIfNeeeded;
  129. + (void)runRequests;
  130. // Handling Proxy autodetection and PAC file downloads
  131. - (BOOL)configureProxies;
  132. - (void)fetchPACFile;
  133. - (void)finishedDownloadingPACFile:(ASIHTTPRequest *)theRequest;
  134. - (void)runPACScript:(NSString *)script;
  135. - (void)timeOutPACRead;
  136. - (void)useDataFromCache;
  137. // Called to update the size of a partial download when starting a request, or retrying after a timeout
  138. - (void)updatePartialDownloadSize;
  139. #if TARGET_OS_IPHONE
  140. + (void)registerForNetworkReachabilityNotifications;
  141. + (void)unsubscribeFromNetworkReachabilityNotifications;
  142. // Called when the status of the network changes
  143. + (void)reachabilityChanged:(NSNotification *)note;
  144. #endif
  145. #if NS_BLOCKS_AVAILABLE
  146. - (void)performBlockOnMainThread:(ASIBasicBlock)block;
  147. - (void)releaseBlocksOnMainThread;
  148. + (void)releaseBlocks:(NSArray *)blocks;
  149. - (void)callBlock:(ASIBasicBlock)block;
  150. #endif
  151. @property (assign) BOOL complete;
  152. @property (retain) NSArray *responseCookies;
  153. @property (assign) int responseStatusCode;
  154. @property (retain, nonatomic) NSDate *lastActivityTime;
  155. @property (assign) unsigned long long partialDownloadSize;
  156. @property (assign, nonatomic) unsigned long long uploadBufferSize;
  157. @property (retain, nonatomic) NSOutputStream *postBodyWriteStream;
  158. @property (retain, nonatomic) NSInputStream *postBodyReadStream;
  159. @property (assign, nonatomic) unsigned long long lastBytesRead;
  160. @property (assign, nonatomic) unsigned long long lastBytesSent;
  161. @property (retain) NSRecursiveLock *cancelledLock;
  162. @property (retain, nonatomic) NSOutputStream *fileDownloadOutputStream;
  163. @property (retain, nonatomic) NSOutputStream *inflatedFileDownloadOutputStream;
  164. @property (assign) int authenticationRetryCount;
  165. @property (assign) int proxyAuthenticationRetryCount;
  166. @property (assign, nonatomic) BOOL updatedProgress;
  167. @property (assign, nonatomic) BOOL needsRedirect;
  168. @property (assign, nonatomic) int redirectCount;
  169. @property (retain, nonatomic) NSData *compressedPostBody;
  170. @property (retain, nonatomic) NSString *compressedPostBodyFilePath;
  171. @property (retain) NSString *authenticationRealm;
  172. @property (retain) NSString *proxyAuthenticationRealm;
  173. @property (retain) NSString *responseStatusMessage;
  174. @property (assign) BOOL inProgress;
  175. @property (assign) int retryCount;
  176. @property (assign) BOOL willRetryRequest;
  177. @property (assign) BOOL connectionCanBeReused;
  178. @property (retain, nonatomic) NSMutableDictionary *connectionInfo;
  179. @property (retain, nonatomic) NSInputStream *readStream;
  180. @property (assign) ASIAuthenticationState authenticationNeeded;
  181. @property (assign, nonatomic) BOOL readStreamIsScheduled;
  182. @property (assign, nonatomic) BOOL downloadComplete;
  183. @property (retain) NSNumber *requestID;
  184. @property (assign, nonatomic) NSString *runLoopMode;
  185. @property (retain, nonatomic) NSTimer *statusTimer;
  186. @property (assign) BOOL didUseCachedResponse;
  187. @property (retain, nonatomic) NSURL *redirectURL;
  188. @property (assign, nonatomic) BOOL isPACFileRequest;
  189. @property (retain, nonatomic) ASIHTTPRequest *PACFileRequest;
  190. @property (retain, nonatomic) NSInputStream *PACFileReadStream;
  191. @property (retain, nonatomic) NSMutableData *PACFileData;
  192. @property (assign, nonatomic, setter=setSynchronous:) BOOL isSynchronous;
  193. @end
  194. @implementation ASIHTTPRequest
  195. #pragma mark init / dealloc
  196. + (void)initialize
  197. {
  198. if (self == [ASIHTTPRequest class]) {
  199. persistentConnectionsPool = [[NSMutableArray alloc] init];
  200. connectionsLock = [[NSRecursiveLock alloc] init];
  201. progressLock = [[NSRecursiveLock alloc] init];
  202. bandwidthThrottlingLock = [[NSLock alloc] init];
  203. sessionCookiesLock = [[NSRecursiveLock alloc] init];
  204. sessionCredentialsLock = [[NSRecursiveLock alloc] init];
  205. delegateAuthenticationLock = [[NSRecursiveLock alloc] init];
  206. bandwidthUsageTracker = [[NSMutableArray alloc] initWithCapacity:5];
  207. ASIRequestTimedOutError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestTimedOutErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request timed out",NSLocalizedDescriptionKey,nil]];
  208. ASIAuthenticationError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIAuthenticationErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Authentication needed",NSLocalizedDescriptionKey,nil]];
  209. ASIRequestCancelledError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIRequestCancelledErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request was cancelled",NSLocalizedDescriptionKey,nil]];
  210. ASIUnableToCreateRequestError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASIUnableToCreateRequestErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create request (bad url?)",NSLocalizedDescriptionKey,nil]];
  211. ASITooMuchRedirectionError = [[NSError alloc] initWithDomain:NetworkRequestErrorDomain code:ASITooMuchRedirectionErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"The request failed because it redirected too many times",NSLocalizedDescriptionKey,nil]];
  212. sharedQueue = [[NSOperationQueue alloc] init];
  213. [sharedQueue setMaxConcurrentOperationCount:4];
  214. }
  215. }
  216. - (id)initWithURL:(NSURL *)newURL
  217. {
  218. self = [self init];
  219. [self setRequestMethod:@"GET"];
  220. [self setRunLoopMode:NSDefaultRunLoopMode];
  221. [self setShouldAttemptPersistentConnection:YES];
  222. [self setPersistentConnectionTimeoutSeconds:60.0];
  223. [self setShouldPresentCredentialsBeforeChallenge:YES];
  224. [self setShouldRedirect:YES];
  225. [self setShowAccurateProgress:YES];
  226. [self setShouldResetDownloadProgress:YES];
  227. [self setShouldResetUploadProgress:YES];
  228. [self setAllowCompressedResponse:YES];
  229. [self setShouldWaitToInflateCompressedResponses:YES];
  230. [self setDefaultResponseEncoding:NSISOLatin1StringEncoding];
  231. [self setShouldPresentProxyAuthenticationDialog:YES];
  232. [self setTimeOutSeconds:[ASIHTTPRequest defaultTimeOutSeconds]];
  233. [self setUseSessionPersistence:YES];
  234. [self setUseCookiePersistence:YES];
  235. [self setValidatesSecureCertificate:YES];
  236. [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]];
  237. [self setDidStartSelector:@selector(requestStarted:)];
  238. [self setDidReceiveResponseHeadersSelector:@selector(request:didReceiveResponseHeaders:)];
  239. [self setWillRedirectSelector:@selector(request:willRedirectToURL:)];
  240. [self setDidFinishSelector:@selector(requestFinished:)];
  241. [self setDidFailSelector:@selector(requestFailed:)];
  242. [self setDidReceiveDataSelector:@selector(request:didReceiveData:)];
  243. [self setURL:newURL];
  244. [self setCancelledLock:[[[NSRecursiveLock alloc] init] autorelease]];
  245. [self setDownloadCache:[[self class] defaultCache]];
  246. return self;
  247. }
  248. + (id)requestWithURL:(NSURL *)newURL
  249. {
  250. return [[[self alloc] initWithURL:newURL] autorelease];
  251. }
  252. + (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache
  253. {
  254. return [self requestWithURL:newURL usingCache:cache andCachePolicy:ASIUseDefaultCachePolicy];
  255. }
  256. + (id)requestWithURL:(NSURL *)newURL usingCache:(id <ASICacheDelegate>)cache andCachePolicy:(ASICachePolicy)policy
  257. {
  258. ASIHTTPRequest *request = [[[self alloc] initWithURL:newURL] autorelease];
  259. [request setDownloadCache:cache];
  260. [request setCachePolicy:policy];
  261. return request;
  262. }
  263. - (void)dealloc
  264. {
  265. [self setAuthenticationNeeded:ASINoAuthenticationNeededYet];
  266. if (requestAuthentication) {
  267. CFRelease(requestAuthentication);
  268. }
  269. if (proxyAuthentication) {
  270. CFRelease(proxyAuthentication);
  271. }
  272. if (request) {
  273. CFRelease(request);
  274. }
  275. if (clientCertificateIdentity) {
  276. CFRelease(clientCertificateIdentity);
  277. }
  278. [self cancelLoad];
  279. [redirectURL release];
  280. [statusTimer invalidate];
  281. [statusTimer release];
  282. [queue release];
  283. [userInfo release];
  284. [postBody release];
  285. [compressedPostBody release];
  286. [error release];
  287. [requestHeaders release];
  288. [requestCookies release];
  289. [downloadDestinationPath release];
  290. [temporaryFileDownloadPath release];
  291. [temporaryUncompressedDataDownloadPath release];
  292. [fileDownloadOutputStream release];
  293. [inflatedFileDownloadOutputStream release];
  294. [username release];
  295. [password release];
  296. [domain release];
  297. [authenticationRealm release];
  298. [authenticationScheme release];
  299. [requestCredentials release];
  300. [proxyHost release];
  301. [proxyType release];
  302. [proxyUsername release];
  303. [proxyPassword release];
  304. [proxyDomain release];
  305. [proxyAuthenticationRealm release];
  306. [proxyAuthenticationScheme release];
  307. [proxyCredentials release];
  308. [url release];
  309. [originalURL release];
  310. [lastActivityTime release];
  311. [responseCookies release];
  312. [rawResponseData release];
  313. [responseHeaders release];
  314. [requestMethod release];
  315. [cancelledLock release];
  316. [postBodyFilePath release];
  317. [compressedPostBodyFilePath release];
  318. [postBodyWriteStream release];
  319. [postBodyReadStream release];
  320. [PACurl release];
  321. [clientCertificates release];
  322. [responseStatusMessage release];
  323. [connectionInfo release];
  324. [requestID release];
  325. [dataDecompressor release];
  326. [userAgent release];
  327. #if NS_BLOCKS_AVAILABLE
  328. [self releaseBlocksOnMainThread];
  329. #endif
  330. [super dealloc];
  331. }
  332. #if NS_BLOCKS_AVAILABLE
  333. - (void)releaseBlocksOnMainThread
  334. {
  335. NSMutableArray *blocks = [NSMutableArray array];
  336. if (completionBlock) {
  337. [blocks addObject:completionBlock];
  338. [completionBlock release];
  339. completionBlock = nil;
  340. }
  341. if (failureBlock) {
  342. [blocks addObject:failureBlock];
  343. [failureBlock release];
  344. failureBlock = nil;
  345. }
  346. if (startedBlock) {
  347. [blocks addObject:startedBlock];
  348. [startedBlock release];
  349. startedBlock = nil;
  350. }
  351. if (headersReceivedBlock) {
  352. [blocks addObject:headersReceivedBlock];
  353. [headersReceivedBlock release];
  354. headersReceivedBlock = nil;
  355. }
  356. if (bytesReceivedBlock) {
  357. [blocks addObject:bytesReceivedBlock];
  358. [bytesReceivedBlock release];
  359. bytesReceivedBlock = nil;
  360. }
  361. if (bytesSentBlock) {
  362. [blocks addObject:bytesSentBlock];
  363. [bytesSentBlock release];
  364. bytesSentBlock = nil;
  365. }
  366. if (downloadSizeIncrementedBlock) {
  367. [blocks addObject:downloadSizeIncrementedBlock];
  368. [downloadSizeIncrementedBlock release];
  369. downloadSizeIncrementedBlock = nil;
  370. }
  371. if (uploadSizeIncrementedBlock) {
  372. [blocks addObject:uploadSizeIncrementedBlock];
  373. [uploadSizeIncrementedBlock release];
  374. uploadSizeIncrementedBlock = nil;
  375. }
  376. if (dataReceivedBlock) {
  377. [blocks addObject:dataReceivedBlock];
  378. [dataReceivedBlock release];
  379. dataReceivedBlock = nil;
  380. }
  381. if (proxyAuthenticationNeededBlock) {
  382. [blocks addObject:proxyAuthenticationNeededBlock];
  383. [proxyAuthenticationNeededBlock release];
  384. proxyAuthenticationNeededBlock = nil;
  385. }
  386. if (authenticationNeededBlock) {
  387. [blocks addObject:authenticationNeededBlock];
  388. [authenticationNeededBlock release];
  389. authenticationNeededBlock = nil;
  390. }
  391. [[self class] performSelectorOnMainThread:@selector(releaseBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
  392. }
  393. // Always called on main thread
  394. + (void)releaseBlocks:(NSArray *)blocks
  395. {
  396. // Blocks will be released when this method exits
  397. }
  398. #endif
  399. #pragma mark setup request
  400. - (void)addRequestHeader:(NSString *)header value:(NSString *)value
  401. {
  402. if (!requestHeaders) {
  403. [self setRequestHeaders:[NSMutableDictionary dictionaryWithCapacity:1]];
  404. }
  405. [requestHeaders setObject:value forKey:header];
  406. }
  407. // This function will be called either just before a request starts, or when postLength is needed, whichever comes first
  408. // postLength must be set by the time this function is complete
  409. - (void)buildPostBody
  410. {
  411. if ([self haveBuiltPostBody]) {
  412. return;
  413. }
  414. // Are we submitting the request body from a file on disk
  415. if ([self postBodyFilePath]) {
  416. // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream
  417. if ([self postBodyWriteStream]) {
  418. [[self postBodyWriteStream] close];
  419. [self setPostBodyWriteStream:nil];
  420. }
  421. NSString *path;
  422. if ([self shouldCompressRequestBody]) {
  423. if (![self compressedPostBodyFilePath]) {
  424. [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
  425. NSError *err = nil;
  426. if (![ASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) {
  427. [self failWithError:err];
  428. return;
  429. }
  430. }
  431. path = [self compressedPostBodyFilePath];
  432. } else {
  433. path = [self postBodyFilePath];
  434. }
  435. NSError *err = nil;
  436. [self setPostLength:[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:path error:&err] fileSize]];
  437. if (err) {
  438. [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]]];
  439. return;
  440. }
  441. // Otherwise, we have an in-memory request body
  442. } else {
  443. if ([self shouldCompressRequestBody]) {
  444. NSError *err = nil;
  445. NSData *compressedBody = [ASIDataCompressor compressData:[self postBody] error:&err];
  446. if (err) {
  447. [self failWithError:err];
  448. return;
  449. }
  450. [self setCompressedPostBody:compressedBody];
  451. [self setPostLength:[[self compressedPostBody] length]];
  452. } else {
  453. [self setPostLength:[[self postBody] length]];
  454. }
  455. }
  456. if ([self postLength] > 0) {
  457. if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) {
  458. [self setRequestMethod:@"POST"];
  459. }
  460. [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]];
  461. }
  462. [self setHaveBuiltPostBody:YES];
  463. }
  464. // Sets up storage for the post body
  465. - (void)setupPostBody
  466. {
  467. if ([self shouldStreamPostDataFromDisk]) {
  468. if (![self postBodyFilePath]) {
  469. [self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
  470. [self setDidCreateTemporaryPostDataFile:YES];
  471. }
  472. if (![self postBodyWriteStream]) {
  473. [self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]];
  474. [[self postBodyWriteStream] open];
  475. }
  476. } else {
  477. if (![self postBody]) {
  478. [self setPostBody:[[[NSMutableData alloc] init] autorelease]];
  479. }
  480. }
  481. }
  482. - (void)appendPostData:(NSData *)data
  483. {
  484. [self setupPostBody];
  485. if ([data length] == 0) {
  486. return;
  487. }
  488. if ([self shouldStreamPostDataFromDisk]) {
  489. [[self postBodyWriteStream] write:[data bytes] maxLength:[data length]];
  490. } else {
  491. [[self postBody] appendData:data];
  492. }
  493. }
  494. - (void)appendPostDataFromFile:(NSString *)file
  495. {
  496. [self setupPostBody];
  497. NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease];
  498. [stream open];
  499. NSUInteger bytesRead;
  500. while ([stream hasBytesAvailable]) {
  501. unsigned char buffer[1024*256];
  502. bytesRead = [stream read:buffer maxLength:sizeof(buffer)];
  503. if (bytesRead == 0) {
  504. break;
  505. }
  506. if ([self shouldStreamPostDataFromDisk]) {
  507. [[self postBodyWriteStream] write:buffer maxLength:bytesRead];
  508. } else {
  509. [[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]];
  510. }
  511. }
  512. [stream close];
  513. }
  514. - (NSString *)requestMethod
  515. {
  516. [[self cancelledLock] lock];
  517. NSString *m = requestMethod;
  518. [[self cancelledLock] unlock];
  519. return m;
  520. }
  521. - (void)setRequestMethod:(NSString *)newRequestMethod
  522. {
  523. [[self cancelledLock] lock];
  524. if (requestMethod != newRequestMethod) {
  525. [requestMethod release];
  526. requestMethod = [newRequestMethod retain];
  527. if ([requestMethod isEqualToString:@"POST"] || [requestMethod isEqualToString:@"PUT"] || [postBody length] || postBodyFilePath) {
  528. [self setShouldAttemptPersistentConnection:NO];
  529. }
  530. }
  531. [[self cancelledLock] unlock];
  532. }
  533. - (NSURL *)url
  534. {
  535. [[self cancelledLock] lock];
  536. NSURL *u = url;
  537. [[self cancelledLock] unlock];
  538. return u;
  539. }
  540. - (void)setURL:(NSURL *)newURL
  541. {
  542. [[self cancelledLock] lock];
  543. if ([newURL isEqual:[self url]]) {
  544. [[self cancelledLock] unlock];
  545. return;
  546. }
  547. [url release];
  548. url = [newURL retain];
  549. if (requestAuthentication) {
  550. CFRelease(requestAuthentication);
  551. requestAuthentication = NULL;
  552. }
  553. if (proxyAuthentication) {
  554. CFRelease(proxyAuthentication);
  555. proxyAuthentication = NULL;
  556. }
  557. if (request) {
  558. CFRelease(request);
  559. request = NULL;
  560. }
  561. [self setRedirectURL:nil];
  562. [[self cancelledLock] unlock];
  563. }
  564. - (id)delegate
  565. {
  566. [[self cancelledLock] lock];
  567. id d = delegate;
  568. [[self cancelledLock] unlock];
  569. return d;
  570. }
  571. - (void)setDelegate:(id)newDelegate
  572. {
  573. [[self cancelledLock] lock];
  574. delegate = newDelegate;
  575. [[self cancelledLock] unlock];
  576. }
  577. - (id)queue
  578. {
  579. [[self cancelledLock] lock];
  580. id q = queue;
  581. [[self cancelledLock] unlock];
  582. return q;
  583. }
  584. - (void)setQueue:(id)newQueue
  585. {
  586. [[self cancelledLock] lock];
  587. if (newQueue != queue) {
  588. [queue release];
  589. queue = [newQueue retain];
  590. }
  591. [[self cancelledLock] unlock];
  592. }
  593. #pragma mark get information about this request
  594. // cancel the request - this must be run on the same thread as the request is running on
  595. - (void)cancelOnRequestThread
  596. {
  597. #if DEBUG_REQUEST_STATUS
  598. NSLog(@"[STATUS] Request cancelled: %@",self);
  599. #endif
  600. [[self cancelledLock] lock];
  601. if ([self isCancelled] || [self complete]) {
  602. [[self cancelledLock] unlock];
  603. return;
  604. }
  605. [self failWithError:ASIRequestCancelledError];
  606. [self setComplete:YES];
  607. [self cancelLoad];
  608. CFRetain(self);
  609. [self willChangeValueForKey:@"isCancelled"];
  610. cancelled = YES;
  611. [self didChangeValueForKey:@"isCancelled"];
  612. [[self cancelledLock] unlock];
  613. CFRelease(self);
  614. }
  615. - (void)cancel
  616. {
  617. [self performSelector:@selector(cancelOnRequestThread) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
  618. }
  619. - (void)clearDelegatesAndCancel
  620. {
  621. [[self cancelledLock] lock];
  622. // Clear delegates
  623. [self setDelegate:nil];
  624. [self setQueue:nil];
  625. [self setDownloadProgressDelegate:nil];
  626. [self setUploadProgressDelegate:nil];
  627. #if NS_BLOCKS_AVAILABLE
  628. // Clear blocks
  629. [self releaseBlocksOnMainThread];
  630. #endif
  631. [[self cancelledLock] unlock];
  632. [self cancel];
  633. }
  634. - (BOOL)isCancelled
  635. {
  636. BOOL result;
  637. [[self cancelledLock] lock];
  638. result = cancelled;
  639. [[self cancelledLock] unlock];
  640. return result;
  641. }
  642. // Call this method to get the received data as an NSString. Don't use for binary data!
  643. - (NSString *)responseString
  644. {
  645. NSData *data = [self responseData];
  646. if (!data) {
  647. return nil;
  648. }
  649. return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
  650. }
  651. - (BOOL)isResponseCompressed
  652. {
  653. NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
  654. return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
  655. }
  656. - (NSData *)responseData
  657. {
  658. if ([self isResponseCompressed] && [self shouldWaitToInflateCompressedResponses]) {
  659. return [ASIDataDecompressor uncompressData:[self rawResponseData] error:NULL];
  660. } else {
  661. return [self rawResponseData];
  662. }
  663. return nil;
  664. }
  665. #pragma mark running a request
  666. - (void)startSynchronous
  667. {
  668. #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
  669. NSLog(@"[STATUS] Starting synchronous request %@",self);
  670. #endif
  671. [self setSynchronous:YES];
  672. [self setRunLoopMode:ASIHTTPRequestRunLoopMode];
  673. [self setInProgress:YES];
  674. if (![self isCancelled] && ![self complete]) {
  675. [self main];
  676. while (!complete) {
  677. [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]];
  678. }
  679. }
  680. [self setInProgress:NO];
  681. }
  682. - (void)start
  683. {
  684. [self setInProgress:YES];
  685. [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO];
  686. }
  687. - (void)startAsynchronous
  688. {
  689. #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING
  690. NSLog(@"[STATUS] Starting asynchronous request %@",self);
  691. #endif
  692. [sharedQueue addOperation:self];
  693. }
  694. #pragma mark concurrency
  695. - (BOOL)isConcurrent
  696. {
  697. return YES;
  698. }
  699. - (BOOL)isFinished
  700. {
  701. return finished;
  702. }
  703. - (BOOL)isExecuting {
  704. return [self inProgress];
  705. }
  706. #pragma mark request logic
  707. // Create the request
  708. - (void)main
  709. {
  710. @try {
  711. [[self cancelledLock] lock];
  712. #if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
  713. if ([ASIHTTPRequest isMultitaskingSupported] && [self shouldContinueWhenAppEntersBackground]) {
  714. backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  715. // Synchronize the cleanup call on the main thread in case
  716. // the task actually finishes at around the same time.
  717. dispatch_async(dispatch_get_main_queue(), ^{
  718. if (backgroundTask != UIBackgroundTaskInvalid)
  719. {
  720. [[UIApplication sharedApplication] endBackgroundTask:backgroundTask];
  721. backgroundTask = UIBackgroundTaskInvalid;
  722. [self cancel];
  723. }
  724. });
  725. }];
  726. }
  727. #endif
  728. // A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed.
  729. if ([self error]) {
  730. [self setComplete:YES];
  731. [self markAsFinished];
  732. return;
  733. }
  734. [self setComplete:NO];
  735. [self setDidUseCachedResponse:NO];
  736. if (![self url]) {
  737. [self failWithError:ASIUnableToCreateRequestError];
  738. return;
  739. }
  740. // Must call before we create the request so that the request method can be set if needs be
  741. if (![self mainRequest]) {
  742. [self buildPostBody];
  743. }
  744. if (![[self requestMethod] isEqualToString:@"GET"]) {
  745. [self setDownloadCache:nil];
  746. }
  747. // If we're redirecting, we'll already have a CFHTTPMessageRef
  748. if (request) {
  749. CFRelease(request);
  750. }
  751. // Create a new HTTP request.
  752. request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, (CFStringRef)[self requestMethod], (CFURLRef)[self url], [self useHTTPVersionOne] ? kCFHTTPVersion1_0 : kCFHTTPVersion1_1);
  753. if (!request) {
  754. [self failWithError:ASIUnableToCreateRequestError];
  755. return;
  756. }
  757. //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
  758. if ([self mainRequest]) {
  759. [[self mainRequest] buildRequestHeaders];
  760. }
  761. // 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)
  762. [self buildRequestHeaders];
  763. if ([self downloadCache]) {
  764. // If this request should use the default policy, set its policy to the download cache's default policy
  765. if (![self cachePolicy]) {
  766. [self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
  767. }
  768. // If have have cached data that is valid for this request, use that and stop
  769. if ([[self downloadCache] canUseCachedDataForRequest:self]) {
  770. [self useDataFromCache];
  771. return;
  772. }
  773. // 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
  774. if ([self cachePolicy] & (ASIAskServerIfModifiedWhenStaleCachePolicy|ASIAskServerIfModifiedCachePolicy)) {
  775. NSDictionary *cachedHeaders = [[self downloadCache] cachedResponseHeadersForURL:[self url]];
  776. if (cachedHeaders) {
  777. NSString *etag = [cachedHeaders objectForKey:@"Etag"];
  778. if (etag) {
  779. [[self requestHeaders] setObject:etag forKey:@"If-None-Match"];
  780. }
  781. NSString *lastModified = [cachedHeaders objectForKey:@"Last-Modified"];
  782. if (lastModified) {
  783. [[self requestHeaders] setObject:lastModified forKey:@"If-Modified-Since"];
  784. }
  785. }
  786. }
  787. }
  788. [self applyAuthorizationHeader];
  789. NSString *header;
  790. for (header in [self requestHeaders]) {
  791. CFHTTPMessageSetHeaderFieldValue(request, (CFStringRef)header, (CFStringRef)[[self requestHeaders] objectForKey:header]);
  792. }
  793. // If we immediately have access to proxy settings, start the request
  794. // Otherwise, we'll start downloading the proxy PAC file, and call startRequest once that process is complete
  795. if ([self configureProxies]) {
  796. [self startRequest];
  797. }
  798. } @catch (NSException *exception) {
  799. NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]];
  800. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]];
  801. } @finally {
  802. [[self cancelledLock] unlock];
  803. }
  804. }
  805. - (void)applyAuthorizationHeader
  806. {
  807. // Do we want to send credentials before we are asked for them?
  808. if (![self shouldPresentCredentialsBeforeChallenge]) {
  809. #if DEBUG_HTTP_AUTHENTICATION
  810. NSLog(@"[AUTH] Request %@ will not send credentials to the server until it asks for them",self);
  811. #endif
  812. return;
  813. }
  814. NSDictionary *credentials = nil;
  815. // Do we already have an auth header?
  816. if (![[self requestHeaders] objectForKey:@"Authorization"]) {
  817. // If we have basic authentication explicitly set and a username and password set on the request, add a basic auth header
  818. if ([self username] && [self password] && [[self authenticationScheme] isEqualToString:(NSString *)kCFHTTPAuthenticationSchemeBasic]) {
  819. [self addBasicAuthenticationHeaderWithUsername:[self username] andPassword:[self password]];
  820. #if DEBUG_HTTP_AUTHENTICATION
  821. 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);
  822. #endif
  823. } else {
  824. // See if we have any cached credentials we can use in the session store
  825. if ([self useSessionPersistence]) {
  826. credentials = [self findSessionAuthenticationCredentials];
  827. if (credentials) {
  828. // When the Authentication key is set, the credentials were stored after an authentication challenge, so we can let CFNetwork apply them
  829. // (credentials for Digest and NTLM will always be stored like this)
  830. if ([credentials objectForKey:@"Authentication"]) {
  831. // If we've already talked to this server and have valid credentials, let's apply them to the request
  832. if (CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
  833. [self setAuthenticationScheme:[credentials objectForKey:@"AuthenticationScheme"]];
  834. #if DEBUG_HTTP_AUTHENTICATION
  835. NSLog(@"[AUTH] Request %@ found cached credentials (%@), will reuse without waiting for an authentication challenge",self,[credentials objectForKey:@"AuthenticationScheme"]);
  836. #endif
  837. } else {
  838. [[self class] removeAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
  839. #if DEBUG_HTTP_AUTHENTICATION
  840. 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);
  841. #endif
  842. }
  843. // If the Authentication key is not set, these credentials were stored after a username and password set on a previous request passed basic authentication
  844. // When this happens, we'll need to create the Authorization header ourselves
  845. } else {
  846. NSDictionary *usernameAndPassword = [credentials objectForKey:@"Credentials"];
  847. [self addBasicAuthenticationHeaderWithUsername:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationUsername] andPassword:[usernameAndPassword objectForKey:(NSString *)kCFHTTPAuthenticationPassword]];
  848. #if DEBUG_HTTP_AUTHENTICATION
  849. NSLog(@"[AUTH] Request %@ found cached BASIC credentials from a previous request. Will send credentials without waiting for an authentication challenge",self);
  850. #endif
  851. }
  852. }
  853. }
  854. }
  855. }
  856. // Apply proxy authentication credentials
  857. if ([self useSessionPersistence]) {
  858. credentials = [self findSessionProxyAuthenticationCredentials];
  859. if (credentials) {
  860. if (!CFHTTPMessageApplyCredentialDictionary(request, (CFHTTPAuthenticationRef)[credentials objectForKey:@"Authentication"], (CFDictionaryRef)[credentials objectForKey:@"Credentials"], NULL)) {
  861. [[self class] removeProxyAuthenticationCredentialsFromSessionStore:[credentials objectForKey:@"Credentials"]];
  862. }
  863. }
  864. }
  865. }
  866. - (void)applyCookieHeader
  867. {
  868. // Add cookies from the persistent (mac os global) store
  869. if ([self useCookiePersistence]) {
  870. NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[[self url] absoluteURL]];
  871. if (cookies) {
  872. [[self requestCookies] addObjectsFromArray:cookies];
  873. }
  874. }
  875. // Apply request cookies
  876. NSArray *cookies;
  877. if ([self mainRequest]) {
  878. cookies = [[self mainRequest] requestCookies];
  879. } else {
  880. cookies = [self requestCookies];
  881. }
  882. if ([cookies count] > 0) {
  883. NSHTTPCookie *cookie;
  884. NSString *cookieHeader = nil;
  885. for (cookie in cookies) {
  886. if (!cookieHeader) {
  887. cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]];
  888. } else {
  889. cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]];
  890. }
  891. }
  892. if (cookieHeader) {
  893. [self addRequestHeader:@"Cookie" value:cookieHeader];
  894. }
  895. }
  896. }
  897. - (void)buildRequestHeaders
  898. {
  899. if ([self haveBuiltRequestHeaders]) {
  900. return;
  901. }
  902. [self setHaveBuiltRequestHeaders:YES];
  903. if ([self mainRequest]) {
  904. for (NSString *header in [[self mainRequest] requestHeaders]) {
  905. [self addRequestHeader:header value:[[[self mainRequest] requestHeaders] valueForKey:header]];
  906. }
  907. return;
  908. }
  909. [self applyCookieHeader];
  910. // Build and set the user agent string if the request does not already have a custom user agent specified
  911. if (![[self requestHeaders] objectForKey:@"User-Agent"]) {
  912. NSString *userAgentString = [self userAgent];
  913. if (!userAgentString) {
  914. userAgentString = [ASIHTTPRequest defaultUserAgentString];
  915. }
  916. if (userAgentString) {
  917. [self addRequestHeader:@"User-Agent" value:userAgentString];
  918. }
  919. }
  920. // Accept a compressed response
  921. if ([self allowCompressedResponse]) {
  922. [self addRequestHeader:@"Accept-Encoding" value:@"gzip"];
  923. }
  924. // Configure a compressed request body
  925. if ([self shouldCompressRequestBody]) {
  926. [self addRequestHeader:@"Content-Encoding" value:@"gzip"];
  927. }
  928. // Should this request resume an existing download?
  929. [self updatePartialDownloadSize];
  930. if ([self partialDownloadSize]) {
  931. [self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",[self partialDownloadSize]]];
  932. }
  933. }
  934. - (void)updatePartialDownloadSize
  935. {
  936. NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
  937. if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [fileManager fileExistsAtPath:[self temporaryFileDownloadPath]]) {
  938. NSError *err = nil;
  939. [self setPartialDownloadSize:[[fileManager attributesOfItemAtPath:[self temporaryFileDownloadPath] error:&err] fileSize]];
  940. if (err) {
  941. [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]]];
  942. return;
  943. }
  944. }
  945. }
  946. - (void)startRequest
  947. {
  948. if ([self isCancelled]) {
  949. return;
  950. }
  951. [self performSelectorOnMainThread:@selector(requestStarted) withObject:nil waitUntilDone:[NSThread isMainThread]];
  952. [self setDownloadComplete:NO];
  953. [self setComplete:NO];
  954. [self setTotalBytesRead:0];
  955. [self setLastBytesRead:0];
  956. if ([self redirectCount] == 0) {
  957. [self setOriginalURL:[self url]];
  958. }
  959. // If we're retrying a request, let's remove any progress we made
  960. if ([self lastBytesSent] > 0) {
  961. [self removeUploadProgressSoFar];
  962. }
  963. [self setLastBytesSent:0];
  964. [self setContentLength:0];
  965. [self setResponseHeaders:nil];
  966. if (![self downloadDestinationPath]) {
  967. [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]];
  968. }
  969. //
  970. // Create the stream for the request
  971. //
  972. NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
  973. [self setReadStreamIsScheduled:NO];
  974. // Do we need to stream the request body from disk
  975. if ([self shouldStreamPostDataFromDisk] && [self postBodyFilePath] && [fileManager fileExistsAtPath:[self postBodyFilePath]]) {
  976. // Are we gzipping the request body?
  977. if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) {
  978. [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
  979. } else {
  980. [self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
  981. }
  982. [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
  983. } else {
  984. // 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
  985. if ([self postBody] && [[self postBody] length] > 0) {
  986. if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
  987. [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
  988. } else if ([self postBody]) {
  989. [self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
  990. }
  991. [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
  992. } else {
  993. [self setReadStream:[NSMakeCollectable(CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request)) autorelease]];
  994. }
  995. }
  996. if (![self readStream]) {
  997. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to create read stream",NSLocalizedDescriptionKey,nil]]];
  998. return;
  999. }
  1000. //
  1001. // Handle SSL certificate settings
  1002. //
  1003. if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
  1004. NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
  1005. // Tell CFNetwork not to validate SSL certificates
  1006. if (![self validatesSecureCertificate]) {
  1007. [sslProperties setObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
  1008. }
  1009. // Tell CFNetwork to use a client certificate
  1010. if (clientCertificateIdentity) {
  1011. NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
  1012. // The first object in the array is our SecIdentityRef
  1013. [certificates addObject:(id)clientCertificateIdentity];
  1014. // If we've added any additional certificates, add them too
  1015. for (id cert in clientCertificates) {
  1016. [certificates addObject:cert];
  1017. }
  1018. [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
  1019. }
  1020. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
  1021. }
  1022. //
  1023. // Handle proxy settings
  1024. //
  1025. if ([self proxyHost] && [self proxyPort]) {
  1026. NSString *hostKey;
  1027. NSString *portKey;
  1028. if (![self proxyType]) {
  1029. [self setProxyType:(NSString *)kCFProxyTypeHTTP];
  1030. }
  1031. if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
  1032. hostKey = (NSString *)kCFStreamPropertySOCKSProxyHost;
  1033. portKey = (NSString *)kCFStreamPropertySOCKSProxyPort;
  1034. } else {
  1035. hostKey = (NSString *)kCFStreamPropertyHTTPProxyHost;
  1036. portKey = (NSString *)kCFStreamPropertyHTTPProxyPort;
  1037. if ([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
  1038. hostKey = (NSString *)kCFStreamPropertyHTTPSProxyHost;
  1039. portKey = (NSString *)kCFStreamPropertyHTTPSProxyPort;
  1040. }
  1041. }
  1042. NSMutableDictionary *proxyToUse = [NSMutableDictionary dictionaryWithObjectsAndKeys:[self proxyHost],hostKey,[NSNumber numberWithInt:[self proxyPort]],portKey,nil];
  1043. if ([[self proxyType] isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
  1044. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySOCKSProxy, proxyToUse);
  1045. } else {
  1046. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPProxy, proxyToUse);
  1047. }
  1048. }
  1049. //
  1050. // Handle persistent connections
  1051. //
  1052. [ASIHTTPRequest expirePersistentConnections];
  1053. [connectionsLock lock];
  1054. if (![[self url] host] || ![[self url] scheme]) {
  1055. [self setConnectionInfo:nil];
  1056. [self setShouldAttemptPersistentConnection:NO];
  1057. }
  1058. // 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
  1059. NSInputStream *oldStream = nil;
  1060. // Use a persistent connection if possible
  1061. if ([self shouldAttemptPersistentConnection]) {
  1062. // If we are redirecting, we will re-use the current connection only if we are connecting to the same server
  1063. if ([self connectionInfo]) {
  1064. 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]) {
  1065. [self setConnectionInfo:nil];
  1066. // Check if we should have expired this connection
  1067. } else if ([[[self connectionInfo] objectForKey:@"expires"] timeIntervalSinceNow] < 0) {
  1068. #if DEBUG_PERSISTENT_CONNECTIONS
  1069. NSLog(@"[CONNECTION] Not re-using connection #%i because it has expired",[[[self connectionInfo] objectForKey:@"id"] intValue]);
  1070. #endif
  1071. [persistentConnectionsPool removeObject:[self connectionInfo]];
  1072. [self setConnectionInfo:nil];
  1073. } else if ([[self connectionInfo] objectForKey:@"request"] != nil) {
  1074. //Some other request reused this connection already - we'll have to create a new one
  1075. #if DEBUG_PERSISTENT_CONNECTIONS
  1076. 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]);
  1077. #endif
  1078. [self setConnectionInfo:nil];
  1079. }
  1080. }
  1081. if (![self connectionInfo] && [[self url] host] && [[self url] scheme]) { // We must have a proper url with a host and scheme, or this will explode
  1082. // Look for a connection to the same server in the pool
  1083. for (NSMutableDictionary *existingConnection in persistentConnectionsPool) {
  1084. 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]) {
  1085. [self setConnectionInfo:existingConnection];
  1086. }
  1087. }
  1088. }
  1089. if ([[self connectionInfo] objectForKey:@"stream"]) {
  1090. oldStream = [[[self connectionInfo] objectForKey:@"stream"] retain];
  1091. }
  1092. // 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
  1093. if (![self connectionInfo]) {
  1094. [self setConnectionInfo:[NSMutableDictionary dictionary]];
  1095. nextConnectionNumberToCreate++;
  1096. [[self connectionInfo] setObject:[NSNumber numberWithInt:nextConnectionNumberToCreate] forKey:@"id"];
  1097. [[self connectionInfo] setObject:[[self url] host] forKey:@"host"];
  1098. [[self connectionInfo] setObject:[NSNumber numberWithInt:[[[self url] port] intValue]] forKey:@"port"];
  1099. [[self connectionInfo] setObject:[[self url] scheme] forKey:@"scheme"];
  1100. [persistentConnectionsPool addObject:[self connectionInfo]];
  1101. }
  1102. // If we are retrying this request, it will already have a requestID
  1103. if (![self requestID]) {
  1104. nextRequestID++;
  1105. [self setRequestID:[NSNumber numberWithUnsignedInt:nextRequestID]];
  1106. }
  1107. [[self connectionInfo] setObject:[self requestID] forKey:@"request"];
  1108. [[self connectionInfo] setObject:[self readStream] forKey:@"stream"];
  1109. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
  1110. #if DEBUG_PERSISTENT_CONNECTIONS
  1111. NSLog(@"[CONNECTION] Request #%@ will use connection #%i",[self requestID],[[[self connectionInfo] objectForKey:@"id"] intValue]);
  1112. #endif
  1113. // Tag the stream with an id that tells it which connection to use behind the scenes
  1114. // See http://lists.apple.com/archives/macnetworkprog/2008/Dec/msg00001.html for details on this approach
  1115. CFReadStreamSetProperty((CFReadStreamRef)[self readStream], CFSTR("ASIStreamID"), [[self connectionInfo] objectForKey:@"id"]);
  1116. } else {
  1117. #if DEBUG_PERSISTENT_CONNECTIONS
  1118. NSLog(@"[CONNECTION] Request %@ will not use a persistent connection",self);
  1119. #endif
  1120. }
  1121. [connectionsLock unlock];
  1122. // Schedule the stream
  1123. if (![self readStreamIsScheduled] && (!throttleWakeUpTime || [throttleWakeUpTime timeIntervalSinceDate:[NSDate date]] < 0)) {
  1124. [self scheduleReadStream];
  1125. }
  1126. BOOL streamSuccessfullyOpened = NO;
  1127. // Start the HTTP connection
  1128. CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
  1129. if (CFReadStreamSetClient((CFReadStreamRef)[self readStream], kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
  1130. if (CFReadStreamOpen((CFReadStreamRef)[self readStream])) {
  1131. streamSuccessfullyOpened = YES;
  1132. }
  1133. }
  1134. // Here, we'll close the stream that was previously using this connection, if there was one
  1135. // 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
  1136. // http://lists.apple.com/archives/Macnetworkprog/2006/Mar/msg00119.html
  1137. if (oldStream) {
  1138. [oldStream close];
  1139. [oldStream release];
  1140. oldStream = nil;
  1141. }
  1142. if (!streamSuccessfullyOpened) {
  1143. [self setConnectionCanBeReused:NO];
  1144. [self destroyReadStream];
  1145. [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Unable to start HTTP connection",NSLocalizedDescriptionKey,nil]]];
  1146. return;
  1147. }
  1148. if (![self mainRequest]) {
  1149. if ([self shouldResetUploadProgress]) {
  1150. if ([self showAccurateProgress]) {
  1151. [self incrementUploadSizeBy:[self postLength]];
  1152. } else {
  1153. [self incrementUploadSizeBy:1];
  1154. }
  1155. [ASIHTTPRequest updateProgressIndicator:&uploadProgressDelegate withProgress:0 ofTotal:1];
  1156. }
  1157. if ([self shouldResetDownloadProgress] && ![self partialDownloadSize]) {
  1158. [ASIHTTPRequest updateProgressIndicator:&downloadProgressDelegate withProgress:0 ofTotal:1];
  1159. }
  1160. }
  1161. // Record when the request started, so we can timeout if nothing happens
  1162. [self setLastActivityTime:[NSDate date]];
  1163. [self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]];
  1164. [[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]];
  1165. }
  1166. - (void)setStatusTimer:(NSTimer *)timer
  1167. {
  1168. CFRetain(self);
  1169. // We must invalidate t