/core/externals/update-engine/externals/gdata-objectivec-client/Source/HTTPFetcher/GTMHTTPFetcher.m

http://macfuse.googlecode.com/ · Objective C · 2007 lines · 1387 code · 322 blank · 298 comment · 288 complexity · 88f4f5798e8f1a7d6dabe2653776642e MD5 · raw file

Large files are truncated click here to view the full file

  1. /* Copyright (c) 2011 Google Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. //
  16. // GTMHTTPFetcher.m
  17. //
  18. #import "GTMHTTPFetcher.h"
  19. #if GTM_BACKGROUND_FETCHING
  20. #import <UIKit/UIKit.h>
  21. #endif
  22. static id <GTMCookieStorageProtocol> gGTMFetcherStaticCookieStorage = nil;
  23. static Class gGTMFetcherConnectionClass = nil;
  24. NSString *const kGTMHTTPFetcherStartedNotification = @"kGTMHTTPFetcherStartedNotification";
  25. NSString *const kGTMHTTPFetcherStoppedNotification = @"kGTMHTTPFetcherStoppedNotification";
  26. NSString *const kGTMHTTPFetcherRetryDelayStartedNotification = @"kGTMHTTPFetcherRetryDelayStartedNotification";
  27. NSString *const kGTMHTTPFetcherRetryDelayStoppedNotification = @"kGTMHTTPFetcherRetryDelayStoppedNotification";
  28. NSString *const kGTMHTTPFetcherErrorDomain = @"com.google.GTMHTTPFetcher";
  29. NSString *const kGTMHTTPFetcherStatusDomain = @"com.google.HTTPStatus";
  30. NSString *const kGTMHTTPFetcherErrorChallengeKey = @"challenge";
  31. NSString *const kGTMHTTPFetcherStatusDataKey = @"data"; // data returned with a kGTMHTTPFetcherStatusDomain error
  32. // The default max retry interview is 10 minutes for uploads (POST/PUT/PATCH),
  33. // 1 minute for downloads.
  34. static const NSTimeInterval kUnsetMaxRetryInterval = -1;
  35. static const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0;
  36. static const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.;
  37. // delegateQueue callback parameters
  38. static NSString *const kCallbackData = @"data";
  39. static NSString *const kCallbackError = @"error";
  40. //
  41. // GTMHTTPFetcher
  42. //
  43. @interface GTMHTTPFetcher ()
  44. @property (copy) NSString *temporaryDownloadPath;
  45. @property (retain) id <GTMCookieStorageProtocol> cookieStorage;
  46. @property (readwrite, retain) NSData *downloadedData;
  47. #if NS_BLOCKS_AVAILABLE
  48. @property (copy) void (^completionBlock)(NSData *, NSError *);
  49. #endif
  50. - (BOOL)beginFetchMayDelay:(BOOL)mayDelay
  51. mayAuthorize:(BOOL)mayAuthorize;
  52. - (void)failToBeginFetchWithError:(NSError *)error;
  53. - (void)failToBeginFetchDeferWithError:(NSError *)error;
  54. #if GTM_BACKGROUND_FETCHING
  55. - (void)endBackgroundTask;
  56. - (void)backgroundFetchExpired;
  57. #endif
  58. - (BOOL)authorizeRequest;
  59. - (void)authorizer:(id <GTMFetcherAuthorizationProtocol>)auth
  60. request:(NSMutableURLRequest *)request
  61. finishedWithError:(NSError *)error;
  62. - (NSString *)createTempDownloadFilePathForPath:(NSString *)targetPath;
  63. - (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks;
  64. - (BOOL)shouldReleaseCallbacksUponCompletion;
  65. - (void)addCookiesToRequest:(NSMutableURLRequest *)request;
  66. - (void)handleCookiesForResponse:(NSURLResponse *)response;
  67. - (void)invokeFetchCallbacksWithData:(NSData *)data
  68. error:(NSError *)error;
  69. - (void)invokeFetchCallback:(SEL)sel
  70. target:(id)target
  71. data:(NSData *)data
  72. error:(NSError *)error;
  73. - (void)invokeFetchCallbacksOnDelegateQueueWithData:(NSData *)data
  74. error:(NSError *)error;
  75. - (void)invokeOnQueueWithDictionary:(NSDictionary *)dict;
  76. - (void)releaseCallbacks;
  77. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
  78. - (BOOL)shouldRetryNowForStatus:(NSInteger)status error:(NSError *)error;
  79. - (void)destroyRetryTimer;
  80. - (void)beginRetryTimer;
  81. - (void)primeRetryTimerWithNewTimeInterval:(NSTimeInterval)secs;
  82. - (void)sendStopNotificationIfNeeded;
  83. - (void)retryFetch;
  84. - (void)retryTimerFired:(NSTimer *)timer;
  85. @end
  86. @interface GTMHTTPFetcher (GTMHTTPFetcherLoggingInternal)
  87. - (void)setupStreamLogging;
  88. - (void)logFetchWithError:(NSError *)error;
  89. - (void)logNowWithError:(NSError *)error;
  90. @end
  91. @implementation GTMHTTPFetcher
  92. + (GTMHTTPFetcher *)fetcherWithRequest:(NSURLRequest *)request {
  93. return [[[[self class] alloc] initWithRequest:request] autorelease];
  94. }
  95. + (GTMHTTPFetcher *)fetcherWithURL:(NSURL *)requestURL {
  96. return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
  97. }
  98. + (GTMHTTPFetcher *)fetcherWithURLString:(NSString *)requestURLString {
  99. return [self fetcherWithURL:[NSURL URLWithString:requestURLString]];
  100. }
  101. + (void)initialize {
  102. // initialize is guaranteed by the runtime to be called in a
  103. // thread-safe manner
  104. if (!gGTMFetcherStaticCookieStorage) {
  105. Class cookieStorageClass = NSClassFromString(@"GTMCookieStorage");
  106. if (cookieStorageClass) {
  107. gGTMFetcherStaticCookieStorage = [[cookieStorageClass alloc] init];
  108. }
  109. }
  110. }
  111. - (id)init {
  112. return [self initWithRequest:nil];
  113. }
  114. - (id)initWithRequest:(NSURLRequest *)request {
  115. self = [super init];
  116. if (self) {
  117. request_ = [request mutableCopy];
  118. if (gGTMFetcherStaticCookieStorage != nil) {
  119. // The user has compiled with the cookie storage class available;
  120. // default to static cookie storage, so our cookies are independent
  121. // of the cookies of other apps.
  122. [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodStatic];
  123. } else {
  124. // Default to system default cookie storage
  125. [self setCookieStorageMethod:kGTMHTTPFetcherCookieStorageMethodSystemDefault];
  126. }
  127. #if !STRIP_GTM_FETCH_LOGGING
  128. // Encourage developers to set the comment property or use
  129. // setCommentWithFormat: by providing a default string.
  130. comment_ = @"(No fetcher comment set)";
  131. #endif
  132. }
  133. return self;
  134. }
  135. - (id)copyWithZone:(NSZone *)zone {
  136. // disallow use of fetchers in a copy property
  137. [self doesNotRecognizeSelector:_cmd];
  138. return nil;
  139. }
  140. - (NSString *)description {
  141. return [NSString stringWithFormat:@"%@ %p (%@)",
  142. [self class], self, [self.mutableRequest URL]];
  143. }
  144. #if !GTM_IPHONE
  145. - (void)finalize {
  146. [self stopFetchReleasingCallbacks:YES]; // releases connection_, destroys timers
  147. [super finalize];
  148. }
  149. #endif
  150. - (void)dealloc {
  151. #if DEBUG
  152. NSAssert(!isStopNotificationNeeded_,
  153. @"unbalanced fetcher notification for %@", [request_ URL]);
  154. #endif
  155. // Note: if a connection or a retry timer was pending, then this instance
  156. // would be retained by those so it wouldn't be getting dealloc'd,
  157. // hence we don't need to stopFetch here
  158. [request_ release];
  159. [connection_ release];
  160. [downloadedData_ release];
  161. [downloadPath_ release];
  162. [temporaryDownloadPath_ release];
  163. [downloadFileHandle_ release];
  164. [credential_ release];
  165. [proxyCredential_ release];
  166. [postData_ release];
  167. [postStream_ release];
  168. [loggedStreamData_ release];
  169. [response_ release];
  170. #if NS_BLOCKS_AVAILABLE
  171. [completionBlock_ release];
  172. [receivedDataBlock_ release];
  173. [sentDataBlock_ release];
  174. [retryBlock_ release];
  175. #endif
  176. [userData_ release];
  177. [properties_ release];
  178. [delegateQueue_ release];
  179. [runLoopModes_ release];
  180. [fetchHistory_ release];
  181. [cookieStorage_ release];
  182. [authorizer_ release];
  183. [service_ release];
  184. [serviceHost_ release];
  185. [thread_ release];
  186. [retryTimer_ release];
  187. [initialRequestDate_ release];
  188. [comment_ release];
  189. [log_ release];
  190. #if !STRIP_GTM_FETCH_LOGGING
  191. [redirectedFromURL_ release];
  192. [logRequestBody_ release];
  193. [logResponseBody_ release];
  194. #endif
  195. [super dealloc];
  196. }
  197. #pragma mark -
  198. // Begin fetching the URL (or begin a retry fetch). The delegate is retained
  199. // for the duration of the fetch connection.
  200. - (BOOL)beginFetchWithDelegate:(id)delegate
  201. didFinishSelector:(SEL)finishedSelector {
  202. GTMAssertSelectorNilOrImplementedWithArgs(delegate, finishedSelector, @encode(GTMHTTPFetcher *), @encode(NSData *), @encode(NSError *), 0);
  203. GTMAssertSelectorNilOrImplementedWithArgs(delegate, receivedDataSel_, @encode(GTMHTTPFetcher *), @encode(NSData *), 0);
  204. GTMAssertSelectorNilOrImplementedWithArgs(delegate, retrySel_, @encode(GTMHTTPFetcher *), @encode(BOOL), @encode(NSError *), 0);
  205. // We'll retain the delegate only during the outstanding connection (similar
  206. // to what Cocoa does with performSelectorOnMainThread:) and during
  207. // authorization or delays, since the app would crash
  208. // if the delegate was released before the fetch calls back
  209. [self setDelegate:delegate];
  210. finishedSel_ = finishedSelector;
  211. return [self beginFetchMayDelay:YES
  212. mayAuthorize:YES];
  213. }
  214. - (BOOL)beginFetchMayDelay:(BOOL)mayDelay
  215. mayAuthorize:(BOOL)mayAuthorize {
  216. // This is the internal entry point for re-starting fetches
  217. NSError *error = nil;
  218. if (connection_ != nil) {
  219. NSAssert1(connection_ != nil, @"fetch object %@ being reused; this should never happen", self);
  220. goto CannotBeginFetch;
  221. }
  222. if (request_ == nil || [request_ URL] == nil) {
  223. NSAssert(request_ != nil, @"beginFetchWithDelegate requires a request with a URL");
  224. goto CannotBeginFetch;
  225. }
  226. self.downloadedData = nil;
  227. downloadedLength_ = 0;
  228. if (mayDelay && service_) {
  229. BOOL shouldFetchNow = [service_ fetcherShouldBeginFetching:self];
  230. if (!shouldFetchNow) {
  231. // the fetch is deferred, but will happen later
  232. return YES;
  233. }
  234. }
  235. NSString *effectiveHTTPMethod = [request_ valueForHTTPHeaderField:@"X-HTTP-Method-Override"];
  236. if (effectiveHTTPMethod == nil) {
  237. effectiveHTTPMethod = [request_ HTTPMethod];
  238. }
  239. BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil
  240. || [effectiveHTTPMethod isEqual:@"GET"]);
  241. if (postData_ || postStream_) {
  242. if (isEffectiveHTTPGet) {
  243. [request_ setHTTPMethod:@"POST"];
  244. isEffectiveHTTPGet = NO;
  245. }
  246. if (postData_) {
  247. [request_ setHTTPBody:postData_];
  248. } else {
  249. if ([self respondsToSelector:@selector(setupStreamLogging)]) {
  250. [self performSelector:@selector(setupStreamLogging)];
  251. }
  252. [request_ setHTTPBodyStream:postStream_];
  253. }
  254. }
  255. // We authorize after setting up the http method and body in the request
  256. // because OAuth 1 may need to sign the request body
  257. if (mayAuthorize && authorizer_) {
  258. BOOL isAuthorized = [authorizer_ isAuthorizedRequest:request_];
  259. if (!isAuthorized) {
  260. // authorization needed
  261. return [self authorizeRequest];
  262. }
  263. }
  264. [fetchHistory_ updateRequest:request_ isHTTPGet:isEffectiveHTTPGet];
  265. // set the default upload or download retry interval, if necessary
  266. if (isRetryEnabled_
  267. && maxRetryInterval_ <= kUnsetMaxRetryInterval) {
  268. if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) {
  269. [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval];
  270. } else {
  271. [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval];
  272. }
  273. }
  274. [self addCookiesToRequest:request_];
  275. if (downloadPath_ != nil) {
  276. // downloading to a path, so create a temporary file and a file handle for
  277. // downloading
  278. NSString *tempPath = [self createTempDownloadFilePathForPath:downloadPath_];
  279. BOOL didCreate = [[NSData data] writeToFile:tempPath
  280. options:0
  281. error:&error];
  282. if (!didCreate) goto CannotBeginFetch;
  283. [self setTemporaryDownloadPath:tempPath];
  284. NSFileHandle *fh = [NSFileHandle fileHandleForWritingAtPath:tempPath];
  285. if (fh == nil) goto CannotBeginFetch;
  286. [self setDownloadFileHandle:fh];
  287. }
  288. // finally, start the connection
  289. Class connectionClass = [[self class] connectionClass];
  290. NSOperationQueue *delegateQueue = delegateQueue_;
  291. if (delegateQueue &&
  292. ![connectionClass instancesRespondToSelector:@selector(setDelegateQueue:)]) {
  293. // NSURLConnection has no setDelegateQueue: on iOS 4 and Mac OS X 10.5.
  294. delegateQueue = nil;
  295. self.delegateQueue = nil;
  296. }
  297. #if DEBUG && TARGET_OS_IPHONE
  298. BOOL isPreIOS6 = (NSFoundationVersionNumber <= 890.1);
  299. if (isPreIOS6 && delegateQueue) {
  300. NSLog(@"GTMHTTPFetcher delegateQueue not safe in iOS 5");
  301. }
  302. #endif
  303. if (downloadFileHandle_ != nil) {
  304. // Downloading to a file, so downloadedData_ remains nil.
  305. } else {
  306. self.downloadedData = [NSMutableData data];
  307. }
  308. hasConnectionEnded_ = NO;
  309. if ([runLoopModes_ count] == 0 && delegateQueue == nil) {
  310. // No custom callback modes or queue were specified, so start the connection
  311. // on the current run loop in the current mode
  312. connection_ = [[connectionClass connectionWithRequest:request_
  313. delegate:self] retain];
  314. } else {
  315. // Specify callbacks be on an operation queue or on the current run loop
  316. // in the specified modes
  317. connection_ = [[connectionClass alloc] initWithRequest:request_
  318. delegate:self
  319. startImmediately:NO];
  320. if (delegateQueue) {
  321. [connection_ performSelector:@selector(setDelegateQueue:)
  322. withObject:delegateQueue];
  323. } else if (runLoopModes_) {
  324. NSRunLoop *rl = [NSRunLoop currentRunLoop];
  325. for (NSString *mode in runLoopModes_) {
  326. [connection_ scheduleInRunLoop:rl forMode:mode];
  327. }
  328. }
  329. [connection_ start];
  330. }
  331. if (!connection_) {
  332. NSAssert(connection_ != nil, @"beginFetchWithDelegate could not create a connection");
  333. self.downloadedData = nil;
  334. goto CannotBeginFetch;
  335. }
  336. #if GTM_BACKGROUND_FETCHING
  337. backgroundTaskIdentifer_ = 0; // UIBackgroundTaskInvalid is 0 on iOS 4
  338. if (shouldFetchInBackground_) {
  339. // For iOS 3 compatibility, ensure that UIApp supports backgrounding
  340. UIApplication *app = [UIApplication sharedApplication];
  341. if ([app respondsToSelector:@selector(beginBackgroundTaskWithExpirationHandler:)]) {
  342. // Tell UIApplication that we want to continue even when the app is in the
  343. // background.
  344. NSThread *thread = delegateQueue_ ? nil : [NSThread currentThread];
  345. backgroundTaskIdentifer_ = [app beginBackgroundTaskWithExpirationHandler:^{
  346. // Background task expiration callback - this block is always invoked by
  347. // UIApplication on the main thread.
  348. if (thread) {
  349. // Run the user's callbacks on the thread used to start the
  350. // fetch.
  351. [self performSelector:@selector(backgroundFetchExpired)
  352. onThread:thread
  353. withObject:nil
  354. waitUntilDone:YES];
  355. } else {
  356. // backgroundFetchExpired invokes callbacks on the provided delegate
  357. // queue.
  358. [self backgroundFetchExpired];
  359. }
  360. }];
  361. }
  362. }
  363. #endif
  364. if (!initialRequestDate_) {
  365. initialRequestDate_ = [[NSDate alloc] init];
  366. }
  367. // Once connection_ is non-nil we can send the start notification
  368. isStopNotificationNeeded_ = YES;
  369. NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
  370. [defaultNC postNotificationName:kGTMHTTPFetcherStartedNotification
  371. object:self];
  372. return YES;
  373. CannotBeginFetch:
  374. [self failToBeginFetchDeferWithError:error];
  375. return NO;
  376. }
  377. - (void)failToBeginFetchDeferWithError:(NSError *)error {
  378. if (delegateQueue_) {
  379. // Deferring will happen by the callback being invoked on the specified
  380. // queue.
  381. [self failToBeginFetchWithError:error];
  382. } else {
  383. // No delegate queue has been specified, so put the callback
  384. // on an appropriate run loop.
  385. NSArray *modes = (runLoopModes_ ? runLoopModes_ :
  386. [NSArray arrayWithObject:NSRunLoopCommonModes]);
  387. [self performSelector:@selector(failToBeginFetchWithError:)
  388. onThread:[NSThread currentThread]
  389. withObject:error
  390. waitUntilDone:NO
  391. modes:modes];
  392. }
  393. }
  394. - (void)failToBeginFetchWithError:(NSError *)error {
  395. if (error == nil) {
  396. error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain
  397. code:kGTMHTTPFetcherErrorDownloadFailed
  398. userInfo:nil];
  399. }
  400. [[self retain] autorelease]; // In case the callback releases us
  401. [self invokeFetchCallbacksOnDelegateQueueWithData:nil
  402. error:error];
  403. [self releaseCallbacks];
  404. [service_ fetcherDidStop:self];
  405. self.authorizer = nil;
  406. if (temporaryDownloadPath_) {
  407. [[NSFileManager defaultManager] removeItemAtPath:temporaryDownloadPath_
  408. error:NULL];
  409. self.temporaryDownloadPath = nil;
  410. }
  411. }
  412. #if GTM_BACKGROUND_FETCHING
  413. - (void)backgroundFetchExpired {
  414. // On background expiration, we stop the fetch and invoke the callbacks
  415. NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain
  416. code:kGTMHTTPFetcherErrorBackgroundExpiration
  417. userInfo:nil];
  418. [self invokeFetchCallbacksOnDelegateQueueWithData:nil
  419. error:error];
  420. @synchronized(self) {
  421. // Stopping the fetch here will indirectly call endBackgroundTask
  422. [self stopFetchReleasingCallbacks:NO];
  423. [self releaseCallbacks];
  424. self.authorizer = nil;
  425. }
  426. }
  427. - (void)endBackgroundTask {
  428. @synchronized(self) {
  429. // Whenever the connection stops or background execution expires,
  430. // we need to tell UIApplication we're done
  431. if (backgroundTaskIdentifer_) {
  432. // If backgroundTaskIdentifer_ is non-zero, we know we're on iOS 4
  433. UIApplication *app = [UIApplication sharedApplication];
  434. [app endBackgroundTask:backgroundTaskIdentifer_];
  435. backgroundTaskIdentifer_ = 0;
  436. }
  437. }
  438. }
  439. #endif // GTM_BACKGROUND_FETCHING
  440. - (BOOL)authorizeRequest {
  441. id authorizer = self.authorizer;
  442. SEL asyncAuthSel = @selector(authorizeRequest:delegate:didFinishSelector:);
  443. if ([authorizer respondsToSelector:asyncAuthSel]) {
  444. SEL callbackSel = @selector(authorizer:request:finishedWithError:);
  445. [authorizer authorizeRequest:request_
  446. delegate:self
  447. didFinishSelector:callbackSel];
  448. return YES;
  449. } else {
  450. NSAssert(authorizer == nil, @"invalid authorizer for fetch");
  451. // No authorizing possible, and authorizing happens only after any delay;
  452. // just begin fetching
  453. return [self beginFetchMayDelay:NO
  454. mayAuthorize:NO];
  455. }
  456. }
  457. - (void)authorizer:(id <GTMFetcherAuthorizationProtocol>)auth
  458. request:(NSMutableURLRequest *)request
  459. finishedWithError:(NSError *)error {
  460. if (error != nil) {
  461. // We can't fetch without authorization
  462. [self failToBeginFetchDeferWithError:error];
  463. } else {
  464. [self beginFetchMayDelay:NO
  465. mayAuthorize:NO];
  466. }
  467. }
  468. #if NS_BLOCKS_AVAILABLE
  469. - (BOOL)beginFetchWithCompletionHandler:(void (^)(NSData *data, NSError *error))handler {
  470. self.completionBlock = handler;
  471. // The user may have called setDelegate: earlier if they want to use other
  472. // delegate-style callbacks during the fetch; otherwise, the delegate is nil,
  473. // which is fine.
  474. return [self beginFetchWithDelegate:[self delegate]
  475. didFinishSelector:nil];
  476. }
  477. #endif
  478. - (NSString *)createTempDownloadFilePathForPath:(NSString *)targetPath {
  479. NSString *tempDir = nil;
  480. #if (!TARGET_OS_IPHONE && (MAC_OS_X_VERSION_MAX_ALLOWED >= 1060))
  481. // Find an appropriate directory for the download, ideally on the same disk
  482. // as the final target location so the temporary file won't have to be moved
  483. // to a different disk.
  484. //
  485. // Available in SDKs for 10.6 and iOS 4
  486. //
  487. // Oct 2011: We previously also used URLForDirectory for
  488. // (TARGET_OS_IPHONE && (__IPHONE_OS_VERSION_MAX_ALLOWED >= 40000))
  489. // but that is returning a non-temporary directory for iOS, unfortunately
  490. SEL sel = @selector(URLForDirectory:inDomain:appropriateForURL:create:error:);
  491. if ([NSFileManager instancesRespondToSelector:sel]) {
  492. NSError *error = nil;
  493. NSURL *targetURL = [NSURL fileURLWithPath:targetPath];
  494. NSFileManager *fileMgr = [NSFileManager defaultManager];
  495. NSURL *tempDirURL = [fileMgr URLForDirectory:NSItemReplacementDirectory
  496. inDomain:NSUserDomainMask
  497. appropriateForURL:targetURL
  498. create:YES
  499. error:&error];
  500. tempDir = [tempDirURL path];
  501. }
  502. #endif
  503. if (tempDir == nil) {
  504. tempDir = NSTemporaryDirectory();
  505. }
  506. static unsigned int counter = 0;
  507. NSString *name = [NSString stringWithFormat:@"gtmhttpfetcher_%u_%u",
  508. ++counter, (unsigned int) arc4random()];
  509. NSString *result = [tempDir stringByAppendingPathComponent:name];
  510. return result;
  511. }
  512. - (void)addCookiesToRequest:(NSMutableURLRequest *)request {
  513. // Get cookies for this URL from our storage array, if
  514. // we have a storage array
  515. if (cookieStorageMethod_ != kGTMHTTPFetcherCookieStorageMethodSystemDefault
  516. && cookieStorageMethod_ != kGTMHTTPFetcherCookieStorageMethodNone) {
  517. NSArray *cookies = [cookieStorage_ cookiesForURL:[request URL]];
  518. if ([cookies count] > 0) {
  519. NSDictionary *headerFields = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
  520. NSString *cookieHeader = [headerFields objectForKey:@"Cookie"]; // key used in header dictionary
  521. if (cookieHeader) {
  522. [request addValue:cookieHeader forHTTPHeaderField:@"Cookie"]; // header name
  523. }
  524. }
  525. }
  526. }
  527. // Returns YES if this is in the process of fetching a URL, or waiting to
  528. // retry, or waiting for authorization, or waiting to be issued by the
  529. // service object
  530. - (BOOL)isFetching {
  531. if (connection_ != nil || retryTimer_ != nil) return YES;
  532. BOOL isAuthorizing = [authorizer_ isAuthorizingRequest:request_];
  533. if (isAuthorizing) return YES;
  534. BOOL isDelayed = [service_ isDelayingFetcher:self];
  535. return isDelayed;
  536. }
  537. // Returns the status code set in connection:didReceiveResponse:
  538. - (NSInteger)statusCode {
  539. NSInteger statusCode;
  540. if (response_ != nil
  541. && [response_ respondsToSelector:@selector(statusCode)]) {
  542. statusCode = [(NSHTTPURLResponse *)response_ statusCode];
  543. } else {
  544. // Default to zero, in hopes of hinting "Unknown" (we can't be
  545. // sure that things are OK enough to use 200).
  546. statusCode = 0;
  547. }
  548. return statusCode;
  549. }
  550. - (NSDictionary *)responseHeaders {
  551. if (response_ != nil
  552. && [response_ respondsToSelector:@selector(allHeaderFields)]) {
  553. NSDictionary *headers = [(NSHTTPURLResponse *)response_ allHeaderFields];
  554. return headers;
  555. }
  556. return nil;
  557. }
  558. - (void)releaseCallbacks {
  559. [delegate_ autorelease];
  560. delegate_ = nil;
  561. [delegateQueue_ autorelease];
  562. delegateQueue_ = nil;
  563. #if NS_BLOCKS_AVAILABLE
  564. self.completionBlock = nil;
  565. self.sentDataBlock = nil;
  566. self.receivedDataBlock = nil;
  567. self.retryBlock = nil;
  568. #endif
  569. }
  570. // Cancel the fetch of the URL that's currently in progress.
  571. - (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks {
  572. id <GTMHTTPFetcherServiceProtocol> service;
  573. // if the connection or the retry timer is all that's retaining the fetcher,
  574. // we want to be sure this instance survives stopping at least long enough for
  575. // the stack to unwind
  576. [[self retain] autorelease];
  577. [self destroyRetryTimer];
  578. @synchronized(self) {
  579. service = [[service_ retain] autorelease];
  580. if (connection_) {
  581. // in case cancelling the connection calls this recursively, we want
  582. // to ensure that we'll only release the connection and delegate once,
  583. // so first set connection_ to nil
  584. NSURLConnection* oldConnection = connection_;
  585. connection_ = nil;
  586. if (!hasConnectionEnded_) {
  587. [oldConnection cancel];
  588. }
  589. // this may be called in a callback from the connection, so use autorelease
  590. [oldConnection autorelease];
  591. }
  592. } // @synchronized(self)
  593. // send the stopped notification
  594. [self sendStopNotificationIfNeeded];
  595. @synchronized(self) {
  596. [authorizer_ stopAuthorizationForRequest:request_];
  597. if (shouldReleaseCallbacks) {
  598. [self releaseCallbacks];
  599. self.authorizer = nil;
  600. }
  601. if (temporaryDownloadPath_) {
  602. [[NSFileManager defaultManager] removeItemAtPath:temporaryDownloadPath_
  603. error:NULL];
  604. self.temporaryDownloadPath = nil;
  605. }
  606. } // @synchronized(self)
  607. [service fetcherDidStop:self];
  608. #if GTM_BACKGROUND_FETCHING
  609. [self endBackgroundTask];
  610. #endif
  611. }
  612. // External stop method
  613. - (void)stopFetching {
  614. [self stopFetchReleasingCallbacks:YES];
  615. }
  616. - (void)sendStopNotificationIfNeeded {
  617. BOOL sendNow = NO;
  618. @synchronized(self) {
  619. if (isStopNotificationNeeded_) {
  620. isStopNotificationNeeded_ = NO;
  621. sendNow = YES;
  622. }
  623. }
  624. if (sendNow) {
  625. NSNotificationCenter *defaultNC = [NSNotificationCenter defaultCenter];
  626. [defaultNC postNotificationName:kGTMHTTPFetcherStoppedNotification
  627. object:self];
  628. }
  629. }
  630. - (void)retryFetch {
  631. [self stopFetchReleasingCallbacks:NO];
  632. [self beginFetchWithDelegate:delegate_
  633. didFinishSelector:finishedSel_];
  634. }
  635. - (void)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds {
  636. NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  637. // Loop until the callbacks have been called and released, and until
  638. // the connection is no longer pending, or until the timeout has expired
  639. BOOL isMainThread = [NSThread isMainThread];
  640. while ((!hasConnectionEnded_
  641. #if NS_BLOCKS_AVAILABLE
  642. || completionBlock_ != nil
  643. #endif
  644. || delegate_ != nil)
  645. && [giveUpDate timeIntervalSinceNow] > 0) {
  646. // Run the current run loop 1/1000 of a second to give the networking
  647. // code a chance to work
  648. if (isMainThread || delegateQueue_ == nil) {
  649. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
  650. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  651. } else {
  652. [NSThread sleepForTimeInterval:0.001];
  653. }
  654. }
  655. }
  656. #pragma mark NSURLConnection Delegate Methods
  657. //
  658. // NSURLConnection Delegate Methods
  659. //
  660. // This method just says "follow all redirects", which _should_ be the default behavior,
  661. // According to file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Conceptual/URLLoadingSystem
  662. // but the redirects were not being followed until I added this method. May be
  663. // a bug in the NSURLConnection code, or the documentation.
  664. //
  665. // In OS X 10.4.8 and earlier, the redirect request doesn't
  666. // get the original's headers and body. This causes POSTs to fail.
  667. // So we construct a new request, a copy of the original, with overrides from the
  668. // redirect.
  669. //
  670. // Docs say that if redirectResponse is nil, just return the redirectRequest.
  671. - (NSURLRequest *)connection:(NSURLConnection *)connection
  672. willSendRequest:(NSURLRequest *)redirectRequest
  673. redirectResponse:(NSURLResponse *)redirectResponse {
  674. @synchronized(self) {
  675. if (redirectRequest && redirectResponse) {
  676. // save cookies from the response
  677. [self handleCookiesForResponse:redirectResponse];
  678. NSMutableURLRequest *newRequest = [[request_ mutableCopy] autorelease];
  679. // copy the URL
  680. NSURL *redirectURL = [redirectRequest URL];
  681. NSURL *url = [newRequest URL];
  682. // disallow scheme changes (say, from https to http)
  683. NSString *redirectScheme = [url scheme];
  684. NSString *newScheme = [redirectURL scheme];
  685. NSString *newResourceSpecifier = [redirectURL resourceSpecifier];
  686. if ([redirectScheme caseInsensitiveCompare:@"http"] == NSOrderedSame
  687. && newScheme != nil
  688. && [newScheme caseInsensitiveCompare:@"https"] == NSOrderedSame) {
  689. // allow the change from http to https
  690. redirectScheme = newScheme;
  691. }
  692. NSString *newUrlString = [NSString stringWithFormat:@"%@:%@",
  693. redirectScheme, newResourceSpecifier];
  694. NSURL *newURL = [NSURL URLWithString:newUrlString];
  695. [newRequest setURL:newURL];
  696. // any headers in the redirect override headers in the original.
  697. NSDictionary *redirectHeaders = [redirectRequest allHTTPHeaderFields];
  698. for (NSString *key in redirectHeaders) {
  699. NSString *value = [redirectHeaders objectForKey:key];
  700. [newRequest setValue:value forHTTPHeaderField:key];
  701. }
  702. [self addCookiesToRequest:newRequest];
  703. redirectRequest = newRequest;
  704. // log the response we just received
  705. [self setResponse:redirectResponse];
  706. [self logNowWithError:nil];
  707. // update the request for future logging
  708. NSMutableURLRequest *mutable = [[redirectRequest mutableCopy] autorelease];
  709. [self setMutableRequest:mutable];
  710. }
  711. return redirectRequest;
  712. } // @synchronized(self)
  713. }
  714. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
  715. @synchronized(self) {
  716. // This method is called when the server has determined that it
  717. // has enough information to create the NSURLResponse
  718. // it can be called multiple times, for example in the case of a
  719. // redirect, so each time we reset the data.
  720. [downloadedData_ setLength:0];
  721. [downloadFileHandle_ truncateFileAtOffset:0];
  722. downloadedLength_ = 0;
  723. [self setResponse:response];
  724. // Save cookies from the response
  725. [self handleCookiesForResponse:response];
  726. }
  727. }
  728. // handleCookiesForResponse: handles storage of cookies for responses passed to
  729. // connection:willSendRequest:redirectResponse: and connection:didReceiveResponse:
  730. - (void)handleCookiesForResponse:(NSURLResponse *)response {
  731. if (cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodSystemDefault
  732. || cookieStorageMethod_ == kGTMHTTPFetcherCookieStorageMethodNone) {
  733. // do nothing special for NSURLConnection's default storage mechanism
  734. // or when we're ignoring cookies
  735. } else if ([response respondsToSelector:@selector(allHeaderFields)]) {
  736. // grab the cookies from the header as NSHTTPCookies and store them either
  737. // into our static array or into the fetchHistory
  738. NSDictionary *responseHeaderFields = [(NSHTTPURLResponse *)response allHeaderFields];
  739. if (responseHeaderFields) {
  740. NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseHeaderFields
  741. forURL:[response URL]];
  742. if ([cookies count] > 0) {
  743. [cookieStorage_ setCookies:cookies];
  744. }
  745. }
  746. }
  747. }
  748. -(void)connection:(NSURLConnection *)connection
  749. didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
  750. @synchronized(self) {
  751. if ([challenge previousFailureCount] <= 2) {
  752. NSURLCredential *credential = credential_;
  753. if ([[challenge protectionSpace] isProxy] && proxyCredential_ != nil) {
  754. credential = proxyCredential_;
  755. }
  756. // Here, if credential is still nil, then we *could* try to get it from
  757. // NSURLCredentialStorage's defaultCredentialForProtectionSpace:.
  758. // We don't, because we're assuming:
  759. //
  760. // - for server credentials, we only want ones supplied by the program
  761. // calling http fetcher
  762. // - for proxy credentials, if one were necessary and available in the
  763. // keychain, it would've been found automatically by NSURLConnection
  764. // and this challenge delegate method never would've been called
  765. // anyway
  766. if (credential) {
  767. // try the credential
  768. [[challenge sender] useCredential:credential
  769. forAuthenticationChallenge:challenge];
  770. return;
  771. }
  772. } // @synchronized(self)
  773. // If we don't have credentials, or we've already failed auth 3x,
  774. // report the error, putting the challenge as a value in the userInfo
  775. // dictionary.
  776. #if DEBUG
  777. NSAssert(!isCancellingChallenge_, @"isCancellingChallenge_ unexpected");
  778. #endif
  779. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:challenge
  780. forKey:kGTMHTTPFetcherErrorChallengeKey];
  781. NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherErrorDomain
  782. code:kGTMHTTPFetcherErrorAuthenticationChallengeFailed
  783. userInfo:userInfo];
  784. // cancelAuthenticationChallenge seems to indirectly call
  785. // connection:didFailWithError: now, though that isn't documented
  786. //
  787. // We'll use an ivar to make the indirect invocation of the
  788. // delegate method do nothing.
  789. isCancellingChallenge_ = YES;
  790. [[challenge sender] cancelAuthenticationChallenge:challenge];
  791. isCancellingChallenge_ = NO;
  792. [self connection:connection didFailWithError:error];
  793. }
  794. }
  795. - (void)invokeFetchCallbacksWithData:(NSData *)data
  796. error:(NSError *)error {
  797. // To avoid deadlocks, this should not be called inside of @synchronized(self)
  798. id target;
  799. SEL sel;
  800. #if NS_BLOCKS_AVAILABLE
  801. void (^block)(NSData *, NSError *);
  802. #endif
  803. // If -stopFetching is called in another thread directly after this @synchronized stanza finishes
  804. // on this thread, then target and block could be released before being used in this method. So
  805. // retain each until this method is done with them.
  806. @synchronized(self) {
  807. target = [[delegate_ retain] autorelease];
  808. sel = finishedSel_;
  809. #if NS_BLOCKS_AVAILABLE
  810. block = [[completionBlock_ retain] autorelease];
  811. #endif
  812. }
  813. [[self retain] autorelease]; // In case the callback releases us
  814. [self invokeFetchCallback:sel
  815. target:target
  816. data:data
  817. error:error];
  818. #if NS_BLOCKS_AVAILABLE
  819. if (block) {
  820. block(data, error);
  821. }
  822. #endif
  823. }
  824. - (void)invokeFetchCallback:(SEL)sel
  825. target:(id)target
  826. data:(NSData *)data
  827. error:(NSError *)error {
  828. // This method is available to subclasses which may provide a customized
  829. // target pointer.
  830. if (target && sel) {
  831. NSMethodSignature *sig = [target methodSignatureForSelector:sel];
  832. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  833. [invocation setSelector:sel];
  834. [invocation setTarget:target];
  835. [invocation setArgument:&self atIndex:2];
  836. [invocation setArgument:&data atIndex:3];
  837. [invocation setArgument:&error atIndex:4];
  838. [invocation invoke];
  839. }
  840. }
  841. - (void)invokeFetchCallbacksOnDelegateQueueWithData:(NSData *)data
  842. error:(NSError *)error {
  843. // This is called by methods that are not already on the delegateQueue
  844. // (as NSURLConnection callbacks should already be, but other failures
  845. // are not.)
  846. if (!delegateQueue_) {
  847. [self invokeFetchCallbacksWithData:data error:error];
  848. }
  849. // Values may be nil.
  850. NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:2];
  851. [dict setValue:data forKey:kCallbackData];
  852. [dict setValue:error forKey:kCallbackError];
  853. NSInvocationOperation *op =
  854. [[[NSInvocationOperation alloc] initWithTarget:self
  855. selector:@selector(invokeOnQueueWithDictionary:)
  856. object:dict] autorelease];
  857. [delegateQueue_ addOperation:op];
  858. }
  859. - (void)invokeOnQueueWithDictionary:(NSDictionary *)dict {
  860. NSData *data = [dict objectForKey:kCallbackData];
  861. NSError *error = [dict objectForKey:kCallbackError];
  862. [self invokeFetchCallbacksWithData:data error:error];
  863. }
  864. - (void)invokeSentDataCallback:(SEL)sel
  865. target:(id)target
  866. didSendBodyData:(NSInteger)bytesWritten
  867. totalBytesWritten:(NSInteger)totalBytesWritten
  868. totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
  869. if (target && sel) {
  870. NSMethodSignature *sig = [target methodSignatureForSelector:sel];
  871. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  872. [invocation setSelector:sel];
  873. [invocation setTarget:target];
  874. [invocation setArgument:&self atIndex:2];
  875. [invocation setArgument:&bytesWritten atIndex:3];
  876. [invocation setArgument:&totalBytesWritten atIndex:4];
  877. [invocation setArgument:&totalBytesExpectedToWrite atIndex:5];
  878. [invocation invoke];
  879. }
  880. }
  881. - (BOOL)invokeRetryCallback:(SEL)sel
  882. target:(id)target
  883. willRetry:(BOOL)willRetry
  884. error:(NSError *)error {
  885. if (target && sel) {
  886. NSMethodSignature *sig = [target methodSignatureForSelector:sel];
  887. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  888. [invocation setSelector:sel];
  889. [invocation setTarget:target];
  890. [invocation setArgument:&self atIndex:2];
  891. [invocation setArgument:&willRetry atIndex:3];
  892. [invocation setArgument:&error atIndex:4];
  893. [invocation invoke];
  894. [invocation getReturnValue:&willRetry];
  895. }
  896. return willRetry;
  897. }
  898. - (void)connection:(NSURLConnection *)connection
  899. didSendBodyData:(NSInteger)bytesWritten
  900. totalBytesWritten:(NSInteger)totalBytesWritten
  901. totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite {
  902. @synchronized(self) {
  903. SEL sel = [self sentDataSelector];
  904. [self invokeSentDataCallback:sel
  905. target:delegate_
  906. didSendBodyData:bytesWritten
  907. totalBytesWritten:totalBytesWritten
  908. totalBytesExpectedToWrite:totalBytesExpectedToWrite];
  909. #if NS_BLOCKS_AVAILABLE
  910. if (sentDataBlock_) {
  911. sentDataBlock_(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
  912. }
  913. #endif
  914. }
  915. }
  916. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
  917. @synchronized(self) {
  918. #if DEBUG
  919. NSAssert(!hasConnectionEnded_, @"Connection received data after ending");
  920. // The download file handle should be set or the data object allocated
  921. // before the fetch is started.
  922. NSAssert((downloadFileHandle_ == nil) != (downloadedData_ == nil),
  923. @"received data accumulates as either NSData (%d) or"
  924. @" NSFileHandle (%d)",
  925. (downloadedData_ != nil), (downloadFileHandle_ != nil));
  926. #endif
  927. // Hopefully, we'll never see this execute out-of-order, receiving data
  928. // after we've received the finished or failed callback.
  929. if (hasConnectionEnded_) return;
  930. if (downloadFileHandle_ != nil) {
  931. // Append to file
  932. @try {
  933. [downloadFileHandle_ writeData:data];
  934. downloadedLength_ = [downloadFileHandle_ offsetInFile];
  935. }
  936. @catch (NSException *exc) {
  937. // Couldn't write to file, probably due to a full disk
  938. NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[exc reason]
  939. forKey:NSLocalizedDescriptionKey];
  940. NSError *error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
  941. code:kGTMHTTPFetcherErrorFileHandleException
  942. userInfo:userInfo];
  943. [self connection:connection didFailWithError:error];
  944. return;
  945. }
  946. } else {
  947. // append to mutable data
  948. [downloadedData_ appendData:data];
  949. downloadedLength_ = [downloadedData_ length];
  950. }
  951. if (receivedDataSel_) {
  952. [delegate_ performSelector:receivedDataSel_
  953. withObject:self
  954. withObject:downloadedData_];
  955. }
  956. #if NS_BLOCKS_AVAILABLE
  957. if (receivedDataBlock_) {
  958. receivedDataBlock_(downloadedData_);
  959. }
  960. #endif
  961. } // @synchronized(self)
  962. }
  963. // For error 304's ("Not Modified") where we've cached the data, return
  964. // status 200 ("OK") to the caller (but leave the fetcher status as 304)
  965. // and copy the cached data.
  966. //
  967. // For other errors or if there's no cached data, just return the actual status.
  968. - (NSData *)cachedDataForStatus {
  969. if ([self statusCode] == kGTMHTTPFetcherStatusNotModified
  970. && [fetchHistory_ shouldCacheETaggedData]) {
  971. NSData *cachedData = [fetchHistory_ cachedDataForRequest:request_];
  972. return cachedData;
  973. }
  974. return nil;
  975. }
  976. - (NSInteger)statusAfterHandlingNotModifiedError {
  977. NSInteger status = [self statusCode];
  978. NSData *cachedData = [self cachedDataForStatus];
  979. if (cachedData) {
  980. // Forge the status to pass on to the delegate
  981. status = 200;
  982. // Copy our stored data
  983. if (downloadFileHandle_ != nil) {
  984. @try {
  985. // Downloading to a file handle won't save to the cache (the data is
  986. // likely inappropriately large for caching), but will still read from
  987. // the cache, on the unlikely chance that the response was Not Modified
  988. // and the URL response was indeed present in the cache.
  989. [downloadFileHandle_ truncateFileAtOffset:0];
  990. [downloadFileHandle_ writeData:cachedData];
  991. downloadedLength_ = [downloadFileHandle_ offsetInFile];
  992. }
  993. @catch (NSException *) {
  994. // Failed to write data, likely due to lack of disk space
  995. status = kGTMHTTPFetcherErrorFileHandleException;
  996. }
  997. } else {
  998. [downloadedData_ setData:cachedData];
  999. downloadedLength_ = [cachedData length];
  1000. }
  1001. }
  1002. return status;
  1003. }
  1004. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  1005. BOOL shouldStopFetching = YES;
  1006. BOOL shouldSendStopNotification = NO;
  1007. NSError *error = nil;
  1008. NSData *downloadedData;
  1009. #if !STRIP_GTM_FETCH_LOGGING
  1010. BOOL shouldDeferLogging = NO;
  1011. #endif
  1012. BOOL shouldBeginRetryTimer = NO;
  1013. @synchronized(self) {
  1014. // We no longer need to cancel the connection
  1015. hasConnectionEnded_ = YES;
  1016. // Skip caching ETagged results when the data is being saved to a file
  1017. if (downloadFileHandle_ == nil) {
  1018. [fetchHistory_ updateFetchHistoryWithRequest:request_
  1019. response:response_
  1020. downloadedData:downloadedData_];
  1021. } else {
  1022. [fetchHistory_ removeCachedDataForRequest:request_];
  1023. }
  1024. [[self retain] autorelease]; // in case the callback releases us
  1025. NSInteger status = [self statusCode];
  1026. if ([self cachedDataForStatus] != nil) {
  1027. #if !STRIP_GTM_FETCH_LOGGING
  1028. // Log the pre-cache response.
  1029. [self logNowWithError:nil];
  1030. hasLoggedError_ = YES;
  1031. #endif
  1032. status = [self statusAfterHandlingNotModifiedError];
  1033. }
  1034. shouldSendStopNotification = YES;
  1035. if (status >= 0 && status < 300) {
  1036. // success
  1037. if (downloadPath_) {
  1038. // Avoid deleting the downloaded file when the fetch stops
  1039. [downloadFileHandle_ closeFile];
  1040. self.downloadFileHandle = nil;
  1041. NSFileManager *fileMgr = [NSFileManager defaultManager];
  1042. [fileMgr removeItemAtPath:downloadPath_
  1043. error:NULL];
  1044. if ([fileMgr moveItemAtPath:temporaryDownloadPath_
  1045. toPath:downloadPath_
  1046. error:&error]) {
  1047. self.temporaryDownloadPath = nil;
  1048. }
  1049. }
  1050. } else {
  1051. // unsuccessful
  1052. #if !STRIP_GTM_FETCH_LOGGING
  1053. if (!hasLoggedError_) {
  1054. [self logNowWithError:nil];
  1055. hasLoggedError_ = YES;
  1056. }
  1057. #endif
  1058. // Status over 300; retry or notify the delegate of failure
  1059. if ([self shouldRetryNowForStatus:status error:nil]) {
  1060. // retrying
  1061. shouldBeginRetryTimer = YES;
  1062. shouldStopFetching = NO;
  1063. } else {
  1064. NSDictionary *userInfo = nil;
  1065. if ([downloadedData_ length] > 0) {
  1066. userInfo = [NSDictionary dictionaryWithObject:downloadedData_
  1067. forKey:kGTMHTTPFetcherStatusDataKey];
  1068. }
  1069. error = [NSError errorWithDomain:kGTMHTTPFetcherStatusDomain
  1070. code:status
  1071. userInfo:userInfo];
  1072. }
  1073. }
  1074. downloadedData = downloadedData_;
  1075. #if !STRIP_GTM_FETCH_LOGGING
  1076. shouldDeferLogging = shouldDeferResponseBodyLogging_;
  1077. #endif
  1078. } // @synchronized(self)
  1079. if (shouldBeginRetryTimer) {
  1080. [self beginRetryTimer];
  1081. }
  1082. if (shouldSendStopNotification) {
  1083. // We want to send the stop notification before calling the delegate's
  1084. // callback selector, since the callback selector may release all of
  1085. // the fetcher properties that the client is using to track the fetches.
  1086. //
  1087. // We'll also stop now so that, to any observers watching the notifications,
  1088. // it doesn't look like our wait for a retry (which may be long,
  1089. // 30 seconds or more) is part of the network activity.
  1090. [self sendStopNotificationIfNeeded];
  1091. }
  1092. if (shouldStopFetching) {
  1093. // Call the callbacks (outside of the @synchronized to avoid deadlocks.)
  1094. [self invokeFetchCallbacksWithData:downloadedData
  1095. error:error];
  1096. BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion];
  1097. [self stopFetchReleasingCallbacks:shouldRelease];
  1098. }
  1099. #if !STRIP_GTM_FETCH_LOGGING
  1100. @synchronized(self) {
  1101. if (!shouldDeferLogging && !hasLoggedError_) {
  1102. [self logNowWithError:nil];
  1103. }
  1104. }
  1105. #endif
  1106. }
  1107. - (BOOL)shouldReleaseCallbacksUponCompletion {
  1108. // A subclass can override this to keep callbacks around after the
  1109. // connection has finished successfully
  1110. return YES;
  1111. }
  1112. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
  1113. @synchronized(self) {
  1114. // Prevent the failure callback from being called twice, since the stopFetch
  1115. // call below (either the explicit one at the end of this method, or the
  1116. // implicit one when the retry occurs) will release the delegate.
  1117. if (connection_ == nil) return;
  1118. // If this method was invoked indirectly by cancellation of an authentication
  1119. // challenge, defer this until it is called again with the proper error object
  1120. if (isCancellingChallenge_) return;
  1121. // We no longer need to cancel the connection
  1122. hasConnectionEnded_ = YES;
  1123. [self logNowWithError:error];
  1124. }
  1125. // See comment about sendStopNotificationIfNeeded
  1126. // in connectionDidFinishLoading:
  1127. [self sendStopNotificationIfNeeded];
  1128. if ([self shouldRetryNowForStatus:0 error:error]) {
  1129. [self beginRetryTimer];
  1130. } else {
  1131. [[self retain] autorelease]; // in case the callback releases us
  1132. [self invokeFetchCallbacksWithData:nil
  1133. error:error];
  1134. [self stopFetchReleasingCallbacks:YES];
  1135. }
  1136. }
  1137. - (void)logNowWithError:(NSError *)error {
  1138. // If the logging category is available, then log the current request,
  1139. // response, data, and error
  1140. if ([self respondsToSelector:@selector(logFetchWithError:)]) {
  1141. [self performSelector:@selector(logFetchWithError:) withObject:error];
  1142. }
  1143. }
  1144. #pragma mark Retries
  1145. - (BOOL)isRetryError:(NSError *)error {
  1146. struct retryRecord {
  1147. NSString *const domain;
  1148. int code;
  1149. };
  1150. struct retryRecord retries[] = {
  1151. { kGTMHTTPFetcherStatusDomain, 408 }, // request timeout
  1152. { kGTMHTTPFetcherStatusDomain, 503 }, // service unavailable
  1153. { kGTMHTTPFetcherStatusDomain, 504 }, // request timeout
  1154. { NSURLErrorDomain, NSURLErrorTimedOut },
  1155. { NSURLErrorDomain, NSURLErrorNetworkConnectionLost },
  1156. { nil, 0 }
  1157. };
  1158. // NSError's isEqual always returns false for equal but distinct instances
  1159. // of NSError, so we have to compare the domain and code values explicitly
  1160. for (int idx = 0; retries[idx].domain != nil; idx++) {
  1161. if ([[error domain] isEqual:retries[idx].domain]
  1162. && [error code] == retries[idx].code) {
  1163. return YES;
  1164. }
  1165. }
  1166. return NO;
  1167. }
  1168. // shouldRetryNowForStatus:error: returns YES if the user has enabled retries
  1169. // and the status or error is one that is suitable for retrying. "Suitable"
  1170. // means either the isRetryError:'s list contains the status or error, or the
  1171. // user's retrySelector: is present and returns YES when called, or the
  1172. // authorizer may be able to fix.
  1173. - (BOOL)shouldRetryNowForStatus:(NSInteger)status
  1174. error:(NSError *)error {
  1175. // Determine if a refreshed authorizer may avoid an authorization error
  1176. BOOL shouldRetryForAuthRefresh = NO;
  1177. BOOL isFirstAuthError = (authorizer_ != nil)
  1178. && !hasAttemptedAuthRefresh_
  1179. && (status == kGTMHTTPFetcherStatusUnauthorized); // 401
  1180. if (isFirstAuthError) {
  1181. if ([authorizer_ respondsToSelector:@selector(primeForRefresh)]) {
  1182. BOOL hasPrimed = [authorizer_ primeForRefresh];
  1183. if (hasPrimed) {
  1184. shouldRetryForAuthRefresh = YES;
  1185. hasAttemptedAuthRefresh_ = YES;
  1186. [request_ setValue:nil forHTTPHeaderField:@"Authorization"];
  1187. }
  1188. }
  1189. }
  1190. // Determine if we're doing exponential backoff retries
  1191. BOOL shouldDoIntervalRetry = [self isRetryEnabled]
  1192. && ([self nextRetryInterval] < [self maxRetryInterval]);
  1193. if (shouldDoIntervalRetry) {
  1194. // If an explicit max retry interval was set, we expect repeated backoffs to take
  1195. // up to roughly twice that for repeated fast failures. If the initial attempt is
  1196. // already more than 3 times the max retry interval, then failures have taken a long time
  1197. // (such as from network timeouts) so don't retry again to avoid the app becoming
  1198. // unexpectedly unresponsive.
  1199. if (maxRetryInterval_ > kUnsetMaxRetryInterval) {
  1200. NSTimeInterval maxAllowedIntervalBeforeRetry = maxRetryInterval_ * 3;
  1201. NSTimeInterval timeSinceInitialRequest = -[initialRequestDate_ timeIntervalSinceNow];
  1202. if (timeSinceInitialRequest > maxAllowedIntervalBeforeRetry) {
  1203. shouldDoIntervalRetry = NO;
  1204. }
  1205. }
  1206. }
  1207. BOOL willRetry = NO;
  1208. BOOL canRetry = shouldRetryForAuthRefresh || shouldDoIntervalRetry;
  1209. if (canRetry) {
  1210. // Check if this is a retryable error
  1211. if (error == nil) {
  1212. // Make an error for the status
  1213. NSDictionary *userInfo = nil;
  1214. if ([downloadedData_ length] > 0) {
  1215. userInfo = [NSDictionary dictionaryWithObject:downloadedData_
  1216. forKey:kGTMHTTPFetcherStatusDataKey];