/iOS/Pods/GTMSessionFetcher/Source/GTMSessionFetcher.m

https://bitbucket.org/bakke458/dvhndemoapp · Objective C · 4453 lines · 3331 code · 633 blank · 489 comment · 633 complexity · a6bdb28901cb9439b64982b72b991389 MD5 · raw file

  1. /* Copyright 2014 Google Inc. All rights reserved.
  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. #if !defined(__has_feature) || !__has_feature(objc_arc)
  16. #error "This file requires ARC support."
  17. #endif
  18. #import "GTMSessionFetcher.h"
  19. #import <sys/utsname.h>
  20. GTM_ASSUME_NONNULL_BEGIN
  21. NSString *const kGTMSessionFetcherStartedNotification = @"kGTMSessionFetcherStartedNotification";
  22. NSString *const kGTMSessionFetcherStoppedNotification = @"kGTMSessionFetcherStoppedNotification";
  23. NSString *const kGTMSessionFetcherRetryDelayStartedNotification = @"kGTMSessionFetcherRetryDelayStartedNotification";
  24. NSString *const kGTMSessionFetcherRetryDelayStoppedNotification = @"kGTMSessionFetcherRetryDelayStoppedNotification";
  25. NSString *const kGTMSessionFetcherCompletionInvokedNotification = @"kGTMSessionFetcherCompletionInvokedNotification";
  26. NSString *const kGTMSessionFetcherCompletionDataKey = @"data";
  27. NSString *const kGTMSessionFetcherCompletionErrorKey = @"error";
  28. NSString *const kGTMSessionFetcherErrorDomain = @"com.google.GTMSessionFetcher";
  29. NSString *const kGTMSessionFetcherStatusDomain = @"com.google.HTTPStatus";
  30. NSString *const kGTMSessionFetcherStatusDataKey = @"data"; // data returned with a kGTMSessionFetcherStatusDomain error
  31. NSString *const kGTMSessionFetcherNumberOfRetriesDoneKey = @"kGTMSessionFetcherNumberOfRetriesDoneKey";
  32. NSString *const kGTMSessionFetcherElapsedIntervalWithRetriesKey = @"kGTMSessionFetcherElapsedIntervalWithRetriesKey";
  33. static NSString *const kGTMSessionIdentifierPrefix = @"com.google.GTMSessionFetcher";
  34. static NSString *const kGTMSessionIdentifierDestinationFileURLMetadataKey = @"_destURL";
  35. static NSString *const kGTMSessionIdentifierBodyFileURLMetadataKey = @"_bodyURL";
  36. // The default max retry interview is 10 minutes for uploads (POST/PUT/PATCH),
  37. // 1 minute for downloads.
  38. static const NSTimeInterval kUnsetMaxRetryInterval = -1.0;
  39. static const NSTimeInterval kDefaultMaxDownloadRetryInterval = 60.0;
  40. static const NSTimeInterval kDefaultMaxUploadRetryInterval = 60.0 * 10.;
  41. #ifdef GTMSESSION_PERSISTED_DESTINATION_KEY
  42. // Projects using unique class names should also define a unique persisted destination key.
  43. static NSString * const kGTMSessionFetcherPersistedDestinationKey =
  44. GTMSESSION_PERSISTED_DESTINATION_KEY;
  45. #else
  46. static NSString * const kGTMSessionFetcherPersistedDestinationKey =
  47. @"com.google.GTMSessionFetcher.downloads";
  48. #endif
  49. GTM_ASSUME_NONNULL_END
  50. //
  51. // GTMSessionFetcher
  52. //
  53. #if 0
  54. #define GTM_LOG_BACKGROUND_SESSION(...) GTMSESSION_LOG_DEBUG(__VA_ARGS__)
  55. #else
  56. #define GTM_LOG_BACKGROUND_SESSION(...)
  57. #endif
  58. #ifndef GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY
  59. #if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \
  60. || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)
  61. #define GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY 1
  62. #endif
  63. #endif
  64. @interface GTMSessionFetcher ()
  65. @property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadedData;
  66. @property(atomic, strong, readwrite, GTM_NULLABLE) NSData *downloadResumeData;
  67. #if GTM_BACKGROUND_TASK_FETCHING
  68. @property(assign, atomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
  69. #endif
  70. @property(atomic, readwrite, getter=isUsingBackgroundSession) BOOL usingBackgroundSession;
  71. @end
  72. #if !GTMSESSION_BUILD_COMBINED_SOURCES
  73. @interface GTMSessionFetcher (GTMSessionFetcherLoggingInternal)
  74. - (void)logFetchWithError:(NSError *)error;
  75. - (void)logNowWithError:(GTM_NULLABLE NSError *)error;
  76. - (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream;
  77. - (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider:
  78. (GTMSessionFetcherBodyStreamProvider)streamProvider;
  79. @end
  80. #endif // !GTMSESSION_BUILD_COMBINED_SOURCES
  81. GTM_ASSUME_NONNULL_BEGIN
  82. static NSTimeInterval InitialMinRetryInterval(void) {
  83. return 1.0 + ((double)(arc4random_uniform(0x0FFFF)) / (double) 0x0FFFF);
  84. }
  85. static BOOL IsLocalhost(NSString * GTM_NULLABLE_TYPE host) {
  86. // We check if there's host, and then make the comparisons.
  87. if (host == nil) return NO;
  88. return ([host caseInsensitiveCompare:@"localhost"] == NSOrderedSame
  89. || [host isEqual:@"::1"]
  90. || [host isEqual:@"127.0.0.1"]);
  91. }
  92. static GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE gGlobalTestBlock;
  93. @implementation GTMSessionFetcher {
  94. NSMutableURLRequest *_request; // after beginFetch, changed only in delegate callbacks
  95. BOOL _useUploadTask; // immutable after beginFetch
  96. NSURL *_bodyFileURL; // immutable after beginFetch
  97. GTMSessionFetcherBodyStreamProvider _bodyStreamProvider; // immutable after beginFetch
  98. NSURLSession *_session;
  99. BOOL _shouldInvalidateSession; // immutable after beginFetch
  100. NSURLSession *_sessionNeedingInvalidation;
  101. NSURLSessionConfiguration *_configuration;
  102. NSURLSessionTask *_sessionTask;
  103. NSString *_taskDescription;
  104. float _taskPriority;
  105. NSURLResponse *_response;
  106. NSString *_sessionIdentifier;
  107. BOOL _wasCreatedFromBackgroundSession;
  108. BOOL _didCreateSessionIdentifier;
  109. NSString *_sessionIdentifierUUID;
  110. BOOL _userRequestedBackgroundSession;
  111. BOOL _usingBackgroundSession;
  112. NSMutableData * GTM_NULLABLE_TYPE _downloadedData;
  113. NSError *_downloadFinishedError;
  114. NSData *_downloadResumeData; // immutable after construction
  115. NSURL *_destinationFileURL;
  116. int64_t _downloadedLength;
  117. NSURLCredential *_credential; // username & password
  118. NSURLCredential *_proxyCredential; // credential supplied to proxy servers
  119. BOOL _isStopNotificationNeeded; // set when start notification has been sent
  120. BOOL _isUsingTestBlock; // set when a test block was provided (remains set when the block is released)
  121. id _userData; // retained, if set by caller
  122. NSMutableDictionary *_properties; // more data retained for caller
  123. dispatch_queue_t _callbackQueue;
  124. dispatch_group_t _callbackGroup; // read-only after creation
  125. NSOperationQueue *_delegateQueue; // immutable after beginFetch
  126. id<GTMFetcherAuthorizationProtocol> _authorizer; // immutable after beginFetch
  127. // The service object that created and monitors this fetcher, if any.
  128. id<GTMSessionFetcherServiceProtocol> _service; // immutable; set by the fetcher service upon creation
  129. NSString *_serviceHost;
  130. NSInteger _servicePriority; // immutable after beginFetch
  131. BOOL _hasStoppedFetching; // counterpart to _initialBeginFetchDate
  132. BOOL _userStoppedFetching;
  133. BOOL _isRetryEnabled; // user wants auto-retry
  134. NSTimer *_retryTimer;
  135. NSUInteger _retryCount;
  136. NSTimeInterval _maxRetryInterval; // default 60 (download) or 600 (upload) seconds
  137. NSTimeInterval _minRetryInterval; // random between 1 and 2 seconds
  138. NSTimeInterval _retryFactor; // default interval multiplier is 2
  139. NSTimeInterval _lastRetryInterval;
  140. NSDate *_initialBeginFetchDate; // date that beginFetch was first invoked; immutable after initial beginFetch
  141. NSDate *_initialRequestDate; // date of first request to the target server (ignoring auth)
  142. BOOL _hasAttemptedAuthRefresh; // accessed only in shouldRetryNowForStatus:
  143. NSString *_comment; // comment for log
  144. NSString *_log;
  145. #if !STRIP_GTM_FETCH_LOGGING
  146. NSMutableData *_loggedStreamData;
  147. NSURL *_redirectedFromURL;
  148. NSString *_logRequestBody;
  149. NSString *_logResponseBody;
  150. BOOL _hasLoggedError;
  151. BOOL _deferResponseBodyLogging;
  152. #endif
  153. }
  154. #if !GTMSESSION_UNIT_TESTING
  155. + (void)load {
  156. [self fetchersForBackgroundSessions];
  157. }
  158. #endif
  159. + (instancetype)fetcherWithRequest:(GTM_NULLABLE NSURLRequest *)request {
  160. return [[self alloc] initWithRequest:request configuration:nil];
  161. }
  162. + (instancetype)fetcherWithURL:(NSURL *)requestURL {
  163. return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
  164. }
  165. + (instancetype)fetcherWithURLString:(NSString *)requestURLString {
  166. return [self fetcherWithURL:(NSURL *)[NSURL URLWithString:requestURLString]];
  167. }
  168. + (instancetype)fetcherWithDownloadResumeData:(NSData *)resumeData {
  169. GTMSessionFetcher *fetcher = [self fetcherWithRequest:nil];
  170. fetcher.comment = @"Resuming download";
  171. fetcher.downloadResumeData = resumeData;
  172. return fetcher;
  173. }
  174. + (GTM_NULLABLE instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier {
  175. GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier");
  176. NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap];
  177. GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier];
  178. if (!fetcher && [sessionIdentifier hasPrefix:kGTMSessionIdentifierPrefix]) {
  179. fetcher = [self fetcherWithRequest:nil];
  180. [fetcher setSessionIdentifier:sessionIdentifier];
  181. [sessionIdentifierToFetcherMap setObject:fetcher forKey:sessionIdentifier];
  182. fetcher->_wasCreatedFromBackgroundSession = YES;
  183. [fetcher setCommentWithFormat:@"Resuming %@",
  184. fetcher && fetcher->_sessionIdentifierUUID ? fetcher->_sessionIdentifierUUID : @"?"];
  185. }
  186. return fetcher;
  187. }
  188. + (NSMapTable *)sessionIdentifierToFetcherMap {
  189. // TODO: What if a service is involved in creating the fetcher? Currently, when re-creating
  190. // fetchers, if a service was involved, it is not re-created. Should the service maintain a map?
  191. static NSMapTable *gSessionIdentifierToFetcherMap = nil;
  192. static dispatch_once_t onceToken;
  193. dispatch_once(&onceToken, ^{
  194. gSessionIdentifierToFetcherMap = [NSMapTable strongToWeakObjectsMapTable];
  195. });
  196. return gSessionIdentifierToFetcherMap;
  197. }
  198. #if !GTM_ALLOW_INSECURE_REQUESTS
  199. + (BOOL)appAllowsInsecureRequests {
  200. // If the main bundle Info.plist key NSAppTransportSecurity is present, and it specifies
  201. // NSAllowsArbitraryLoads, then we need to explicitly enforce secure schemes.
  202. #if GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY
  203. static BOOL allowsInsecureRequests;
  204. static dispatch_once_t onceToken;
  205. dispatch_once(&onceToken, ^{
  206. NSBundle *mainBundle = [NSBundle mainBundle];
  207. NSDictionary *appTransportSecurity =
  208. [mainBundle objectForInfoDictionaryKey:@"NSAppTransportSecurity"];
  209. allowsInsecureRequests =
  210. [[appTransportSecurity objectForKey:@"NSAllowsArbitraryLoads"] boolValue];
  211. });
  212. return allowsInsecureRequests;
  213. #else
  214. // For builds targeting iOS 8 or 10.10 and earlier, we want to require fetcher
  215. // security checks.
  216. return YES;
  217. #endif // GTM_TARGET_SUPPORTS_APP_TRANSPORT_SECURITY
  218. }
  219. #else // GTM_ALLOW_INSECURE_REQUESTS
  220. + (BOOL)appAllowsInsecureRequests {
  221. return YES;
  222. }
  223. #endif // !GTM_ALLOW_INSECURE_REQUESTS
  224. - (instancetype)init {
  225. return [self initWithRequest:nil configuration:nil];
  226. }
  227. - (instancetype)initWithRequest:(NSURLRequest *)request {
  228. return [self initWithRequest:request configuration:nil];
  229. }
  230. - (instancetype)initWithRequest:(GTM_NULLABLE NSURLRequest *)request
  231. configuration:(GTM_NULLABLE NSURLSessionConfiguration *)configuration {
  232. self = [super init];
  233. if (self) {
  234. if (![NSURLSession class]) {
  235. Class oldFetcherClass = NSClassFromString(@"GTMHTTPFetcher");
  236. if (oldFetcherClass && request) {
  237. self = [[oldFetcherClass alloc] initWithRequest:(NSURLRequest *)request];
  238. } else {
  239. self = nil;
  240. }
  241. return self;
  242. }
  243. #if GTM_BACKGROUND_TASK_FETCHING
  244. _backgroundTaskIdentifier = UIBackgroundTaskInvalid;
  245. #endif
  246. _request = [request mutableCopy];
  247. _configuration = configuration;
  248. NSData *bodyData = request.HTTPBody;
  249. if (bodyData) {
  250. _bodyLength = (int64_t)bodyData.length;
  251. } else {
  252. _bodyLength = NSURLSessionTransferSizeUnknown;
  253. }
  254. _callbackQueue = dispatch_get_main_queue();
  255. _callbackGroup = dispatch_group_create();
  256. _delegateQueue = [NSOperationQueue mainQueue];
  257. _minRetryInterval = InitialMinRetryInterval();
  258. _maxRetryInterval = kUnsetMaxRetryInterval;
  259. _taskPriority = -1.0f; // Valid values if set are 0.0...1.0.
  260. #if !STRIP_GTM_FETCH_LOGGING
  261. // Encourage developers to set the comment property or use
  262. // setCommentWithFormat: by providing a default string.
  263. _comment = @"(No fetcher comment set)";
  264. #endif
  265. }
  266. return self;
  267. }
  268. - (id)copyWithZone:(NSZone *)zone {
  269. // disallow use of fetchers in a copy property
  270. [self doesNotRecognizeSelector:_cmd];
  271. return nil;
  272. }
  273. - (NSString *)description {
  274. NSString *requestStr = self.request.URL.description;
  275. if (requestStr.length == 0) {
  276. if (self.downloadResumeData.length > 0) {
  277. requestStr = @"<download resume data>";
  278. } else if (_wasCreatedFromBackgroundSession) {
  279. requestStr = @"<from bg session>";
  280. } else {
  281. requestStr = @"<no request>";
  282. }
  283. }
  284. return [NSString stringWithFormat:@"%@ %p (%@)", [self class], self, requestStr];
  285. }
  286. - (void)dealloc {
  287. GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded,
  288. @"unbalanced fetcher notification for %@", _request.URL);
  289. [self forgetSessionIdentifierForFetcherWithoutSyncCheck];
  290. // Note: if a session task or a retry timer was pending, then this instance
  291. // would be retained by those so it wouldn't be getting dealloc'd,
  292. // hence we don't need to stopFetch here
  293. }
  294. #pragma mark -
  295. // Begin fetching the URL (or begin a retry fetch). The delegate is retained
  296. // for the duration of the fetch connection.
  297. - (void)beginFetchWithCompletionHandler:(GTM_NULLABLE GTMSessionFetcherCompletionHandler)handler {
  298. GTMSessionCheckNotSynchronized(self);
  299. _completionHandler = [handler copy];
  300. // The user may have called setDelegate: earlier if they want to use other
  301. // delegate-style callbacks during the fetch; otherwise, the delegate is nil,
  302. // which is fine.
  303. [self beginFetchMayDelay:YES mayAuthorize:YES];
  304. }
  305. - (GTMSessionFetcherCompletionHandler)completionHandlerWithTarget:(GTM_NULLABLE_TYPE id)target
  306. didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector {
  307. GTMSessionFetcherAssertValidSelector(target, finishedSelector, @encode(GTMSessionFetcher *),
  308. @encode(NSData *), @encode(NSError *), 0);
  309. GTMSessionFetcherCompletionHandler completionHandler = ^(NSData *data, NSError *error) {
  310. if (target && finishedSelector) {
  311. id selfArg = self; // Placate ARC.
  312. NSMethodSignature *sig = [target methodSignatureForSelector:finishedSelector];
  313. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
  314. [invocation setSelector:(SEL)finishedSelector];
  315. [invocation setTarget:target];
  316. [invocation setArgument:&selfArg atIndex:2];
  317. [invocation setArgument:&data atIndex:3];
  318. [invocation setArgument:&error atIndex:4];
  319. [invocation invoke];
  320. }
  321. };
  322. return completionHandler;
  323. }
  324. - (void)beginFetchWithDelegate:(GTM_NULLABLE_TYPE id)target
  325. didFinishSelector:(GTM_NULLABLE_TYPE SEL)finishedSelector {
  326. GTMSessionCheckNotSynchronized(self);
  327. GTMSessionFetcherCompletionHandler handler = [self completionHandlerWithTarget:target
  328. didFinishSelector:finishedSelector];
  329. [self beginFetchWithCompletionHandler:handler];
  330. }
  331. - (void)beginFetchMayDelay:(BOOL)mayDelay
  332. mayAuthorize:(BOOL)mayAuthorize {
  333. // This is the internal entry point for re-starting fetches.
  334. GTMSessionCheckNotSynchronized(self);
  335. NSMutableURLRequest *fetchRequest = _request; // The request property is now externally immutable.
  336. NSURL *fetchRequestURL = fetchRequest.URL;
  337. NSString *priorSessionIdentifier = self.sessionIdentifier;
  338. // A utility block for creating error objects when we fail to start the fetch.
  339. NSError *(^beginFailureError)(NSInteger) = ^(NSInteger code){
  340. NSString *urlString = fetchRequestURL.absoluteString;
  341. NSDictionary *userInfo = @{
  342. NSURLErrorFailingURLStringErrorKey : (urlString ? urlString : @"(missing URL)")
  343. };
  344. return [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
  345. code:code
  346. userInfo:userInfo];
  347. };
  348. // Catch delegate queue maxConcurrentOperationCount values other than 1, particularly
  349. // NSOperationQueueDefaultMaxConcurrentOperationCount (-1), to avoid the additional complexity
  350. // of simultaneous or out-of-order delegate callbacks.
  351. GTMSESSION_ASSERT_DEBUG(_delegateQueue.maxConcurrentOperationCount == 1,
  352. @"delegate queue %@ should support one concurrent operation, not %zd",
  353. _delegateQueue.name, _delegateQueue.maxConcurrentOperationCount);
  354. if (!_initialBeginFetchDate) {
  355. // This ivar is set only here on the initial beginFetch so need not be synchronized.
  356. _initialBeginFetchDate = [[NSDate alloc] init];
  357. }
  358. if (self.sessionTask != nil) {
  359. // If cached fetcher returned through fetcherWithSessionIdentifier:, then it's
  360. // already begun, but don't consider this a failure, since the user need not know this.
  361. if (self.sessionIdentifier != nil) {
  362. return;
  363. }
  364. GTMSESSION_ASSERT_DEBUG(NO, @"Fetch object %@ being reused; this should never happen", self);
  365. [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)];
  366. return;
  367. }
  368. if (fetchRequestURL == nil && !_downloadResumeData && !priorSessionIdentifier) {
  369. GTMSESSION_ASSERT_DEBUG(NO, @"Beginning a fetch requires a request with a URL");
  370. [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorDownloadFailed)];
  371. return;
  372. }
  373. // We'll respect the user's request for a background session (unless this is
  374. // an upload fetcher, which does its initial request foreground.)
  375. self.usingBackgroundSession = self.useBackgroundSession && [self canFetchWithBackgroundSession];
  376. NSURL *bodyFileURL = self.bodyFileURL;
  377. if (bodyFileURL) {
  378. NSError *fileCheckError;
  379. if (![bodyFileURL checkResourceIsReachableAndReturnError:&fileCheckError]) {
  380. // This assert fires when the file being uploaded no longer exists once
  381. // the fetcher is ready to start the upload.
  382. GTMSESSION_ASSERT_DEBUG_OR_LOG(0, @"Body file is unreachable: %@\n %@",
  383. bodyFileURL.path, fileCheckError);
  384. [self failToBeginFetchWithError:fileCheckError];
  385. return;
  386. }
  387. }
  388. NSString *requestScheme = fetchRequestURL.scheme;
  389. BOOL isDataRequest = [requestScheme isEqual:@"data"];
  390. if (isDataRequest) {
  391. // NSURLSession does not support data URLs in background sessions.
  392. #if DEBUG
  393. if (priorSessionIdentifier || self.sessionIdentifier) {
  394. GTMSESSION_LOG_DEBUG(@"Converting background to foreground session for %@",
  395. fetchRequest);
  396. }
  397. #endif
  398. [self setSessionIdentifierInternal:nil];
  399. self.useBackgroundSession = NO;
  400. }
  401. #if GTM_ALLOW_INSECURE_REQUESTS
  402. BOOL shouldCheckSecurity = NO;
  403. #else
  404. BOOL shouldCheckSecurity = (fetchRequestURL != nil
  405. && !isDataRequest
  406. && [[self class] appAllowsInsecureRequests]);
  407. #endif
  408. if (shouldCheckSecurity) {
  409. // Allow https only for requests, unless overridden by the client.
  410. //
  411. // Non-https requests may too easily be snooped, so we disallow them by default.
  412. //
  413. // file: and data: schemes are usually safe if they are hardcoded in the client or provided
  414. // by a trusted source, but since it's fairly rare to need them, it's safest to make clients
  415. // explicitly whitelist them.
  416. BOOL isSecure =
  417. requestScheme != nil && [requestScheme caseInsensitiveCompare:@"https"] == NSOrderedSame;
  418. if (!isSecure) {
  419. BOOL allowRequest = NO;
  420. NSString *host = fetchRequestURL.host;
  421. // Check schemes first. A file scheme request may be allowed here, or as a localhost request.
  422. for (NSString *allowedScheme in _allowedInsecureSchemes) {
  423. if (requestScheme != nil &&
  424. [requestScheme caseInsensitiveCompare:allowedScheme] == NSOrderedSame) {
  425. allowRequest = YES;
  426. break;
  427. }
  428. }
  429. if (!allowRequest) {
  430. // Check for localhost requests. Security checks only occur for non-https requests, so
  431. // this check won't happen for an https request to localhost.
  432. BOOL isLocalhostRequest = (host.length == 0 && [fetchRequestURL isFileURL]) || IsLocalhost(host);
  433. if (isLocalhostRequest) {
  434. if (self.allowLocalhostRequest) {
  435. allowRequest = YES;
  436. } else {
  437. GTMSESSION_ASSERT_DEBUG(NO, @"Fetch request for localhost but fetcher"
  438. @" allowLocalhostRequest is not set: %@", fetchRequestURL);
  439. }
  440. } else {
  441. GTMSESSION_ASSERT_DEBUG(NO, @"Insecure fetch request has a scheme (%@)"
  442. @" not found in fetcher allowedInsecureSchemes (%@): %@",
  443. requestScheme, _allowedInsecureSchemes ?: @" @[] ", fetchRequestURL);
  444. }
  445. }
  446. if (!allowRequest) {
  447. #if !DEBUG
  448. NSLog(@"Insecure fetch disallowed for %@", fetchRequestURL.description ?: @"nil request URL");
  449. #endif
  450. [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorInsecureRequest)];
  451. return;
  452. }
  453. } // !isSecure
  454. } // (requestURL != nil) && !isDataRequest
  455. if (self.cookieStorage == nil) {
  456. self.cookieStorage = [[self class] staticCookieStorage];
  457. }
  458. BOOL isRecreatingSession = (self.sessionIdentifier != nil) && (fetchRequest == nil);
  459. self.canShareSession = !isRecreatingSession && !self.usingBackgroundSession;
  460. if (!self.session && self.canShareSession) {
  461. self.session = [_service sessionForFetcherCreation];
  462. // If _session is nil, then the service's session creation semaphore will block
  463. // until this fetcher invokes fetcherDidCreateSession: below, so this *must* invoke
  464. // that method, even if the session fails to be created.
  465. }
  466. if (!self.session) {
  467. // Create a session.
  468. if (!_configuration) {
  469. if (priorSessionIdentifier || self.usingBackgroundSession) {
  470. NSString *sessionIdentifier = priorSessionIdentifier;
  471. if (!sessionIdentifier) {
  472. sessionIdentifier = [self createSessionIdentifierWithMetadata:nil];
  473. }
  474. NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap];
  475. [sessionIdentifierToFetcherMap setObject:self forKey:self.sessionIdentifier];
  476. #if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) \
  477. || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)
  478. // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name.
  479. _configuration =
  480. [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier];
  481. #elif (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) \
  482. || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0)
  483. // Do a runtime check to avoid a deprecation warning about using
  484. // +backgroundSessionConfiguration: on iOS 8.
  485. if ([NSURLSessionConfiguration respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) {
  486. // Running on iOS 8+/OS X 10.10+.
  487. _configuration =
  488. [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionIdentifier];
  489. } else {
  490. // Running on iOS 7/OS X 10.9.
  491. _configuration =
  492. [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
  493. }
  494. #else
  495. // Building with an SDK earlier than iOS 8/OS X 10.10.
  496. _configuration =
  497. [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
  498. #endif
  499. self.usingBackgroundSession = YES;
  500. self.canShareSession = NO;
  501. } else {
  502. _configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
  503. }
  504. #if !GTM_ALLOW_INSECURE_REQUESTS
  505. _configuration.TLSMinimumSupportedProtocol = kTLSProtocol12;
  506. #endif
  507. } // !_configuration
  508. _configuration.HTTPCookieStorage = self.cookieStorage;
  509. if (_configurationBlock) {
  510. _configurationBlock(self, _configuration);
  511. }
  512. id<NSURLSessionDelegate> delegate = [_service sessionDelegate];
  513. if (!delegate || !self.canShareSession) {
  514. delegate = self;
  515. }
  516. self.session = [NSURLSession sessionWithConfiguration:_configuration
  517. delegate:delegate
  518. delegateQueue:self.sessionDelegateQueue];
  519. GTMSESSION_ASSERT_DEBUG(self.session, @"Couldn't create session");
  520. // Tell the service about the session created by this fetcher. This also signals the
  521. // service's semaphore to allow other fetchers to request this session.
  522. [_service fetcherDidCreateSession:self];
  523. // If this assertion fires, the client probably tried to use a session identifier that was
  524. // already used. The solution is to make the client use a unique identifier (or better yet let
  525. // the session fetcher assign the identifier).
  526. GTMSESSION_ASSERT_DEBUG(self.session.delegate == delegate, @"Couldn't assign delegate.");
  527. if (self.session) {
  528. BOOL isUsingSharedDelegate = (delegate != self);
  529. if (!isUsingSharedDelegate) {
  530. _shouldInvalidateSession = YES;
  531. }
  532. }
  533. }
  534. if (isRecreatingSession) {
  535. _shouldInvalidateSession = YES;
  536. // Let's make sure there are tasks still running or if not that we get a callback from a
  537. // completed one; otherwise, we assume the tasks failed.
  538. // This is the observed behavior perhaps 25% of the time within the Simulator running 7.0.3 on
  539. // exiting the app after starting an upload and relaunching the app if we manage to relaunch
  540. // after the task has completed, but before the system relaunches us in the background.
  541. [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks,
  542. NSArray *downloadTasks) {
  543. if (dataTasks.count == 0 && uploadTasks.count == 0 && downloadTasks.count == 0) {
  544. double const kDelayInSeconds = 1.0; // We should get progress indication or completion soon
  545. dispatch_time_t checkForFeedbackDelay =
  546. dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDelayInSeconds * NSEC_PER_SEC));
  547. dispatch_after(checkForFeedbackDelay, dispatch_get_main_queue(), ^{
  548. if (!self.sessionTask && !fetchRequest) {
  549. // If our task and/or request haven't been restored, then we assume task feedback lost.
  550. [self removePersistedBackgroundSessionFromDefaults];
  551. NSError *sessionError =
  552. [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
  553. code:GTMSessionFetcherErrorBackgroundFetchFailed
  554. userInfo:nil];
  555. [self failToBeginFetchWithError:sessionError];
  556. }
  557. });
  558. }
  559. }];
  560. return;
  561. }
  562. self.downloadedData = nil;
  563. self.downloadedLength = 0;
  564. if (_servicePriority == NSIntegerMin) {
  565. mayDelay = NO;
  566. }
  567. if (mayDelay && _service) {
  568. BOOL shouldFetchNow = [_service fetcherShouldBeginFetching:self];
  569. if (!shouldFetchNow) {
  570. // The fetch is deferred, but will happen later.
  571. //
  572. // If this session is held by the fetcher service, clear the session now so that we don't
  573. // assume it's still valid after the fetcher is restarted.
  574. if (self.canShareSession) {
  575. self.session = nil;
  576. }
  577. return;
  578. }
  579. }
  580. NSString *effectiveHTTPMethod = [fetchRequest valueForHTTPHeaderField:@"X-HTTP-Method-Override"];
  581. if (effectiveHTTPMethod == nil) {
  582. effectiveHTTPMethod = fetchRequest.HTTPMethod;
  583. }
  584. BOOL isEffectiveHTTPGet = (effectiveHTTPMethod == nil
  585. || [effectiveHTTPMethod isEqual:@"GET"]);
  586. BOOL needsUploadTask = (self.useUploadTask || self.bodyFileURL || self.bodyStreamProvider);
  587. if (_bodyData || self.bodyStreamProvider || fetchRequest.HTTPBodyStream) {
  588. if (isEffectiveHTTPGet) {
  589. fetchRequest.HTTPMethod = @"POST";
  590. isEffectiveHTTPGet = NO;
  591. }
  592. if (_bodyData) {
  593. if (!needsUploadTask) {
  594. fetchRequest.HTTPBody = _bodyData;
  595. }
  596. #if !STRIP_GTM_FETCH_LOGGING
  597. } else if (fetchRequest.HTTPBodyStream) {
  598. if ([self respondsToSelector:@selector(loggedInputStreamForInputStream:)]) {
  599. fetchRequest.HTTPBodyStream =
  600. [self performSelector:@selector(loggedInputStreamForInputStream:)
  601. withObject:fetchRequest.HTTPBodyStream];
  602. }
  603. #endif
  604. }
  605. }
  606. // We authorize after setting up the http method and body in the request
  607. // because OAuth 1 may need to sign the request body
  608. if (mayAuthorize && _authorizer && !isDataRequest) {
  609. BOOL isAuthorized = [_authorizer isAuthorizedRequest:fetchRequest];
  610. if (!isAuthorized) {
  611. // Authorization needed.
  612. //
  613. // If this session is held by the fetcher service, clear the session now so that we don't
  614. // assume it's still valid after authorization completes.
  615. if (self.canShareSession) {
  616. self.session = nil;
  617. }
  618. // Authorizing the request will recursively call this beginFetch:mayDelay:
  619. // or failToBeginFetchWithError:.
  620. [self authorizeRequest];
  621. return;
  622. }
  623. }
  624. // set the default upload or download retry interval, if necessary
  625. if ([self isRetryEnabled] && self.maxRetryInterval <= 0) {
  626. if (isEffectiveHTTPGet || [effectiveHTTPMethod isEqual:@"HEAD"]) {
  627. [self setMaxRetryInterval:kDefaultMaxDownloadRetryInterval];
  628. } else {
  629. [self setMaxRetryInterval:kDefaultMaxUploadRetryInterval];
  630. }
  631. }
  632. // finally, start the connection
  633. NSURLSessionTask *newSessionTask;
  634. BOOL needsDataAccumulator = NO;
  635. if (_downloadResumeData) {
  636. newSessionTask = [_session downloadTaskWithResumeData:_downloadResumeData];
  637. GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask,
  638. @"Failed downloadTaskWithResumeData for %@, resume data %tu bytes",
  639. _session, _downloadResumeData.length);
  640. } else if (_destinationFileURL && !isDataRequest) {
  641. newSessionTask = [_session downloadTaskWithRequest:fetchRequest];
  642. GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed downloadTaskWithRequest for %@, %@",
  643. _session, fetchRequest);
  644. } else if (needsUploadTask) {
  645. if (bodyFileURL) {
  646. newSessionTask = [_session uploadTaskWithRequest:fetchRequest
  647. fromFile:bodyFileURL];
  648. GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask,
  649. @"Failed uploadTaskWithRequest for %@, %@, file %@",
  650. _session, fetchRequest, bodyFileURL.path);
  651. } else if (self.bodyStreamProvider) {
  652. newSessionTask = [_session uploadTaskWithStreamedRequest:fetchRequest];
  653. GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask,
  654. @"Failed uploadTaskWithStreamedRequest for %@, %@",
  655. _session, fetchRequest);
  656. } else {
  657. GTMSESSION_ASSERT_DEBUG_OR_LOG(_bodyData != nil,
  658. @"Upload task needs body data, %@", fetchRequest);
  659. newSessionTask = [_session uploadTaskWithRequest:fetchRequest
  660. fromData:(NSData * GTM_NONNULL_TYPE)_bodyData];
  661. GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask,
  662. @"Failed uploadTaskWithRequest for %@, %@, body data %tu bytes",
  663. _session, fetchRequest, _bodyData.length);
  664. }
  665. needsDataAccumulator = YES;
  666. } else {
  667. newSessionTask = [_session dataTaskWithRequest:fetchRequest];
  668. needsDataAccumulator = YES;
  669. GTMSESSION_ASSERT_DEBUG_OR_LOG(newSessionTask, @"Failed dataTaskWithRequest for %@, %@",
  670. _session, fetchRequest);
  671. }
  672. self.sessionTask = newSessionTask;
  673. if (!newSessionTask) {
  674. // We shouldn't get here; if we're here, an earlier assertion should have fired to explain
  675. // which session task creation failed.
  676. [self failToBeginFetchWithError:beginFailureError(GTMSessionFetcherErrorTaskCreationFailed)];
  677. return;
  678. }
  679. if (needsDataAccumulator && _accumulateDataBlock == nil) {
  680. self.downloadedData = [NSMutableData data];
  681. }
  682. if (_taskDescription) {
  683. newSessionTask.taskDescription = _taskDescription;
  684. }
  685. if (_taskPriority >= 0) {
  686. #if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_10) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) \
  687. || (TARGET_OS_IPHONE && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)
  688. BOOL hasTaskPriority = YES;
  689. #else
  690. BOOL hasTaskPriority = [newSessionTask respondsToSelector:@selector(setPriority:)];
  691. #endif
  692. if (hasTaskPriority) {
  693. newSessionTask.priority = _taskPriority;
  694. }
  695. }
  696. #if GTM_DISABLE_FETCHER_TEST_BLOCK
  697. GTMSESSION_ASSERT_DEBUG(_testBlock == nil && gGlobalTestBlock == nil, @"test blocks disabled");
  698. _testBlock = nil;
  699. #else
  700. if (!_testBlock) {
  701. if (gGlobalTestBlock) {
  702. // Note that the test block may pass nil for all of its response parameters,
  703. // indicating that the fetch should actually proceed. This is useful when the
  704. // global test block has been set, and the app is only testing a specific
  705. // fetcher. The block simulation code will then resume the task.
  706. _testBlock = gGlobalTestBlock;
  707. }
  708. }
  709. _isUsingTestBlock = (_testBlock != nil);
  710. #endif // GTM_DISABLE_FETCHER_TEST_BLOCK
  711. #if GTM_BACKGROUND_TASK_FETCHING
  712. // Background tasks seem to interfere with out-of-process uploads and downloads.
  713. if (!self.skipBackgroundTask && !self.useBackgroundSession) {
  714. // Tell UIApplication that we want to continue even when the app is in the
  715. // background.
  716. id<GTMUIApplicationProtocol> app = [[self class] fetcherUIApplication];
  717. #if DEBUG
  718. NSString *bgTaskName = [NSString stringWithFormat:@"%@-%@",
  719. [self class], fetchRequest.URL.host];
  720. #else
  721. NSString *bgTaskName = @"GTMSessionFetcher";
  722. #endif
  723. __block UIBackgroundTaskIdentifier bgTaskID = [app beginBackgroundTaskWithName:bgTaskName
  724. expirationHandler:^{
  725. // Background task expiration callback - this block is always invoked by
  726. // UIApplication on the main thread.
  727. if (bgTaskID != UIBackgroundTaskInvalid) {
  728. if (bgTaskID == self.backgroundTaskIdentifier) {
  729. self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
  730. }
  731. [app endBackgroundTask:bgTaskID];
  732. }
  733. }];
  734. self.backgroundTaskIdentifier = bgTaskID;
  735. }
  736. #endif
  737. if (!_initialRequestDate) {
  738. _initialRequestDate = [[NSDate alloc] init];
  739. }
  740. // We don't expect to reach here even on retry or auth until a stop notification has been sent
  741. // for the previous task, but we should ensure that we don't unbalance that.
  742. GTMSESSION_ASSERT_DEBUG(!_isStopNotificationNeeded, @"Start notification without a prior stop");
  743. [self sendStopNotificationIfNeeded];
  744. [self addPersistedBackgroundSessionToDefaults];
  745. [self setStopNotificationNeeded:YES];
  746. [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStartedNotification
  747. userInfo:nil
  748. requireAsync:NO];
  749. // The service needs to know our task if it is serving as NSURLSession delegate.
  750. [_service fetcherDidBeginFetching:self];
  751. if (_testBlock) {
  752. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  753. [self simulateFetchForTestBlock];
  754. #endif
  755. } else {
  756. // We resume the session task after posting the notification since the
  757. // delegate callbacks may happen immediately if the fetch is started off
  758. // the main thread or the session delegate queue is on a background thread,
  759. // and we don't want to post a start notification after a premature finish
  760. // of the session task.
  761. [newSessionTask resume];
  762. }
  763. }
  764. NSData * GTM_NULLABLE_TYPE GTMDataFromInputStream(NSInputStream *inputStream, NSError **outError) {
  765. NSMutableData *data = [NSMutableData data];
  766. [inputStream open];
  767. NSInteger numberOfBytesRead = 0;
  768. while ([inputStream hasBytesAvailable]) {
  769. uint8_t buffer[512];
  770. numberOfBytesRead = [inputStream read:buffer maxLength:sizeof(buffer)];
  771. if (numberOfBytesRead > 0) {
  772. [data appendBytes:buffer length:(NSUInteger)numberOfBytesRead];
  773. } else {
  774. break;
  775. }
  776. }
  777. [inputStream close];
  778. NSError *streamError = inputStream.streamError;
  779. if (streamError) {
  780. data = nil;
  781. }
  782. if (outError) {
  783. *outError = streamError;
  784. }
  785. return data;
  786. }
  787. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  788. - (void)simulateFetchForTestBlock {
  789. // This is invoked on the same thread as the beginFetch method was.
  790. //
  791. // Callbacks will all occur on the callback queue.
  792. _testBlock(self, ^(NSURLResponse *response, NSData *responseData, NSError *error) {
  793. // Callback from test block.
  794. if (response == nil && responseData == nil && error == nil) {
  795. // Assume the fetcher should execute rather than be tested.
  796. _testBlock = nil;
  797. _isUsingTestBlock = NO;
  798. [_sessionTask resume];
  799. return;
  800. }
  801. GTMSessionFetcherBodyStreamProvider bodyStreamProvider = self.bodyStreamProvider;
  802. if (bodyStreamProvider) {
  803. bodyStreamProvider(^(NSInputStream *bodyStream){
  804. // Read from the input stream into an NSData buffer. We'll drain the stream
  805. // explicitly on a background queue.
  806. [self invokeOnCallbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
  807. afterUserStopped:NO
  808. block:^{
  809. NSError *streamError;
  810. NSData *streamedData = GTMDataFromInputStream(bodyStream, &streamError);
  811. dispatch_async(dispatch_get_main_queue(), ^{
  812. // Continue callbacks on the main thread, since serial behavior
  813. // is more reliable for tests.
  814. [self simulateDataCallbacksForTestBlockWithBodyData:streamedData
  815. response:response
  816. responseData:responseData
  817. error:(error ?: streamError)];
  818. });
  819. }];
  820. });
  821. } else {
  822. // No input stream; use the supplied data or file URL.
  823. NSURL *bodyFileURL = self.bodyFileURL;
  824. if (bodyFileURL) {
  825. NSError *readError;
  826. _bodyData = [NSData dataWithContentsOfURL:bodyFileURL
  827. options:NSDataReadingMappedIfSafe
  828. error:&readError];
  829. error = readError;
  830. }
  831. // No stream provider.
  832. // In real fetches, nothing happens until the run loop spins, so apps have leeway to
  833. // set callbacks after they call beginFetch. We'll mirror that fetcher behavior by
  834. // delaying callbacks here at least to the next spin of the run loop. That keeps
  835. // immediate, synchronous setting of callback blocks after beginFetch working in tests.
  836. dispatch_async(dispatch_get_main_queue(), ^{
  837. [self simulateDataCallbacksForTestBlockWithBodyData:_bodyData
  838. response:response
  839. responseData:responseData
  840. error:error];
  841. });
  842. }
  843. });
  844. }
  845. - (void)simulateByteTransferReportWithDataLength:(int64_t)totalDataLength
  846. block:(GTMSessionFetcherSendProgressBlock)block {
  847. // This utility method simulates transfer progress with up to three callbacks.
  848. // It is used to call back to any of the progress blocks.
  849. int64_t sendReportSize = totalDataLength / 3 + 1;
  850. int64_t totalSent = 0;
  851. while (totalSent < totalDataLength) {
  852. int64_t bytesRemaining = totalDataLength - totalSent;
  853. sendReportSize = MIN(sendReportSize, bytesRemaining);
  854. totalSent += sendReportSize;
  855. [self invokeOnCallbackQueueUnlessStopped:^{
  856. block(sendReportSize, totalSent, totalDataLength);
  857. }];
  858. }
  859. }
  860. - (void)simulateDataCallbacksForTestBlockWithBodyData:(NSData * GTM_NULLABLE_TYPE)bodyData
  861. response:(NSURLResponse *)response
  862. responseData:(NSData *)suppliedData
  863. error:(NSError *)suppliedError {
  864. __block NSData *responseData = suppliedData;
  865. __block NSError *responseError = suppliedError;
  866. // This method does the test simulation of callbacks once the upload
  867. // and download data are known.
  868. @synchronized(self) {
  869. GTMSessionMonitorSynchronized(self);
  870. // Get copies of ivars we'll access in async invocations. This simulation assumes
  871. // they won't change during fetcher execution.
  872. NSURL *destinationFileURL = _destinationFileURL;
  873. GTMSessionFetcherWillRedirectBlock willRedirectBlock = _willRedirectBlock;
  874. GTMSessionFetcherDidReceiveResponseBlock didReceiveResponseBlock = _didReceiveResponseBlock;
  875. GTMSessionFetcherSendProgressBlock sendProgressBlock = _sendProgressBlock;
  876. GTMSessionFetcherDownloadProgressBlock downloadProgressBlock = _downloadProgressBlock;
  877. GTMSessionFetcherAccumulateDataBlock accumulateDataBlock = _accumulateDataBlock;
  878. GTMSessionFetcherReceivedProgressBlock receivedProgressBlock = _receivedProgressBlock;
  879. GTMSessionFetcherWillCacheURLResponseBlock willCacheURLResponseBlock =
  880. _willCacheURLResponseBlock;
  881. // Simulate receipt of redirection.
  882. if (willRedirectBlock) {
  883. [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES
  884. block:^{
  885. willRedirectBlock((NSHTTPURLResponse *)response, _request,
  886. ^(NSURLRequest *redirectRequest) {
  887. // For simulation, we'll assume the app will just continue.
  888. });
  889. }];
  890. }
  891. // If the fetcher has a challenge block, simulate a challenge.
  892. //
  893. // It might be nice to eventually let the user determine which testBlock
  894. // fetches get challenged rather than always executing the supplied
  895. // challenge block.
  896. if (_challengeBlock) {
  897. [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES
  898. block:^{
  899. if (_challengeBlock) {
  900. NSURL *requestURL = _request.URL;
  901. NSString *host = requestURL.host;
  902. NSURLProtectionSpace *pspace =
  903. [[NSURLProtectionSpace alloc] initWithHost:host
  904. port:requestURL.port.integerValue
  905. protocol:requestURL.scheme
  906. realm:nil
  907. authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
  908. id<NSURLAuthenticationChallengeSender> unusedSender =
  909. (id<NSURLAuthenticationChallengeSender>)[NSNull null];
  910. NSURLAuthenticationChallenge *challenge =
  911. [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:pspace
  912. proposedCredential:nil
  913. previousFailureCount:0
  914. failureResponse:nil
  915. error:nil
  916. sender:unusedSender];
  917. _challengeBlock(self, challenge, ^(NSURLSessionAuthChallengeDisposition disposition,
  918. NSURLCredential * GTM_NULLABLE_TYPE credential){
  919. // We could change the responseData and responseError based on the disposition,
  920. // but it's easier for apps to just supply the expected data and error
  921. // directly to the test block. So this simulation ignores the disposition.
  922. });
  923. }
  924. }];
  925. }
  926. // Simulate receipt of an initial response.
  927. if (didReceiveResponseBlock) {
  928. [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES
  929. block:^{
  930. didReceiveResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) {
  931. // For simulation, we'll assume the disposition is to continue.
  932. });
  933. }];
  934. }
  935. // Simulate reporting send progress.
  936. if (sendProgressBlock) {
  937. [self simulateByteTransferReportWithDataLength:(int64_t)bodyData.length
  938. block:^(int64_t bytesSent,
  939. int64_t totalBytesSent,
  940. int64_t totalBytesExpectedToSend) {
  941. // This is invoked on the callback queue unless stopped.
  942. sendProgressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend);
  943. }];
  944. }
  945. if (destinationFileURL) {
  946. // Simulate download to file progress.
  947. if (downloadProgressBlock) {
  948. [self simulateByteTransferReportWithDataLength:(int64_t)responseData.length
  949. block:^(int64_t bytesDownloaded,
  950. int64_t totalBytesDownloaded,
  951. int64_t totalBytesExpectedToDownload) {
  952. // This is invoked on the callback queue unless stopped.
  953. downloadProgressBlock(bytesDownloaded, totalBytesDownloaded,
  954. totalBytesExpectedToDownload);
  955. }];
  956. }
  957. NSError *writeError;
  958. [responseData writeToURL:destinationFileURL
  959. options:NSDataWritingAtomic
  960. error:&writeError];
  961. if (writeError) {
  962. // Tell the test code that writing failed.
  963. responseError = writeError;
  964. }
  965. } else {
  966. // Simulate download to NSData progress.
  967. if (accumulateDataBlock) {
  968. if (responseData) {
  969. [self invokeOnCallbackQueueUnlessStopped:^{
  970. accumulateDataBlock(responseData);
  971. }];
  972. }
  973. } else {
  974. _downloadedData = [responseData mutableCopy];
  975. }
  976. if (receivedProgressBlock) {
  977. [self simulateByteTransferReportWithDataLength:(int64_t)responseData.length
  978. block:^(int64_t bytesReceived,
  979. int64_t totalBytesReceived,
  980. int64_t totalBytesExpectedToReceive) {
  981. // This is invoked on the callback queue unless stopped.
  982. receivedProgressBlock(bytesReceived, totalBytesReceived);
  983. }];
  984. }
  985. if (willCacheURLResponseBlock) {
  986. // Simulate letting the client inspect and alter the cached response.
  987. NSData *cachedData = responseData ?: [[NSData alloc] init]; // Always have non-nil data.
  988. NSCachedURLResponse *cachedResponse =
  989. [[NSCachedURLResponse alloc] initWithResponse:response
  990. data:cachedData];
  991. [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:YES
  992. block:^{
  993. willCacheURLResponseBlock(cachedResponse, ^(NSCachedURLResponse *responseToCache){
  994. // The app may provide an alternative response, or nil to defeat caching.
  995. });
  996. }];
  997. }
  998. }
  999. _response = response;
  1000. } // @synchronized(self)
  1001. NSOperationQueue *queue = self.sessionDelegateQueue;
  1002. [queue addOperationWithBlock:^{
  1003. // Rather than invoke failToBeginFetchWithError: we want to simulate completion of
  1004. // a connection that started and ended, so we'll call down to finishWithError:
  1005. NSInteger status = responseError ? responseError.code : 200;
  1006. if (status >= 200 && status <= 399) {
  1007. [self finishWithError:nil shouldRetry:NO];
  1008. } else {
  1009. [self shouldRetryNowForStatus:status
  1010. error:responseError
  1011. forceAssumeRetry:NO
  1012. response:^(BOOL shouldRetry) {
  1013. [self finishWithError:responseError shouldRetry:shouldRetry];
  1014. }];
  1015. }
  1016. }];
  1017. }
  1018. #endif // !GTM_DISABLE_FETCHER_TEST_BLOCK
  1019. - (void)setSessionTask:(NSURLSessionTask *)sessionTask {
  1020. @synchronized(self) {
  1021. GTMSessionMonitorSynchronized(self);
  1022. if (_sessionTask != sessionTask) {
  1023. _sessionTask = sessionTask;
  1024. if (_sessionTask) {
  1025. // Request could be nil on restoring this fetcher from a background session.
  1026. if (!_request) {
  1027. _request = [_sessionTask.originalRequest mutableCopy];
  1028. }
  1029. }
  1030. }
  1031. } // @synchronized(self)
  1032. }
  1033. - (NSURLSessionTask * GTM_NULLABLE_TYPE)sessionTask {
  1034. @synchronized(self) {
  1035. GTMSessionMonitorSynchronized(self);
  1036. return _sessionTask;
  1037. } // @synchronized(self)
  1038. }
  1039. + (NSUserDefaults *)fetcherUserDefaults {
  1040. static NSUserDefaults *gFetcherUserDefaults = nil;
  1041. static dispatch_once_t onceToken;
  1042. dispatch_once(&onceToken, ^{
  1043. Class fetcherUserDefaultsClass = NSClassFromString(@"GTMSessionFetcherUserDefaultsFactory");
  1044. if (fetcherUserDefaultsClass) {
  1045. gFetcherUserDefaults = [fetcherUserDefaultsClass fetcherUserDefaults];
  1046. } else {
  1047. gFetcherUserDefaults = [NSUserDefaults standardUserDefaults];
  1048. }
  1049. });
  1050. return gFetcherUserDefaults;
  1051. }
  1052. - (void)addPersistedBackgroundSessionToDefaults {
  1053. NSString *sessionIdentifier = self.sessionIdentifier;
  1054. if (!sessionIdentifier) {
  1055. return;
  1056. }
  1057. NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions];
  1058. if ([oldBackgroundSessions containsObject:_sessionIdentifier]) {
  1059. return;
  1060. }
  1061. NSMutableArray *newBackgroundSessions =
  1062. [NSMutableArray arrayWithArray:oldBackgroundSessions];
  1063. [newBackgroundSessions addObject:sessionIdentifier];
  1064. GTM_LOG_BACKGROUND_SESSION(@"Add to background sessions: %@", newBackgroundSessions);
  1065. NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults];
  1066. [userDefaults setObject:newBackgroundSessions
  1067. forKey:kGTMSessionFetcherPersistedDestinationKey];
  1068. [userDefaults synchronize];
  1069. }
  1070. - (void)removePersistedBackgroundSessionFromDefaults {
  1071. NSString *sessionIdentifier = self.sessionIdentifier;
  1072. if (!sessionIdentifier) return;
  1073. NSArray *oldBackgroundSessions = [[self class] activePersistedBackgroundSessions];
  1074. if (!oldBackgroundSessions) {
  1075. return;
  1076. }
  1077. NSMutableArray *newBackgroundSessions =
  1078. [NSMutableArray arrayWithArray:oldBackgroundSessions];
  1079. NSUInteger sessionIndex = [newBackgroundSessions indexOfObject:sessionIdentifier];
  1080. if (sessionIndex == NSNotFound) {
  1081. return;
  1082. }
  1083. [newBackgroundSessions removeObjectAtIndex:sessionIndex];
  1084. GTM_LOG_BACKGROUND_SESSION(@"Remove from background sessions: %@", newBackgroundSessions);
  1085. NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults];
  1086. if (newBackgroundSessions.count == 0) {
  1087. [userDefaults removeObjectForKey:kGTMSessionFetcherPersistedDestinationKey];
  1088. } else {
  1089. [userDefaults setObject:newBackgroundSessions
  1090. forKey:kGTMSessionFetcherPersistedDestinationKey];
  1091. }
  1092. [userDefaults synchronize];
  1093. }
  1094. + (GTM_NULLABLE NSArray *)activePersistedBackgroundSessions {
  1095. NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults];
  1096. NSArray *oldBackgroundSessions =
  1097. [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey];
  1098. if (oldBackgroundSessions.count == 0) {
  1099. return nil;
  1100. }
  1101. NSMutableArray *activeBackgroundSessions = nil;
  1102. NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap];
  1103. for (NSString *sessionIdentifier in oldBackgroundSessions) {
  1104. GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier];
  1105. if (fetcher) {
  1106. if (!activeBackgroundSessions) {
  1107. activeBackgroundSessions = [[NSMutableArray alloc] init];
  1108. }
  1109. [activeBackgroundSessions addObject:sessionIdentifier];
  1110. }
  1111. }
  1112. return activeBackgroundSessions;
  1113. }
  1114. + (NSArray *)fetchersForBackgroundSessions {
  1115. NSUserDefaults *userDefaults = [[self class] fetcherUserDefaults];
  1116. NSArray *backgroundSessions =
  1117. [userDefaults arrayForKey:kGTMSessionFetcherPersistedDestinationKey];
  1118. NSMapTable *sessionIdentifierToFetcherMap = [self sessionIdentifierToFetcherMap];
  1119. NSMutableArray *fetchers = [NSMutableArray array];
  1120. for (NSString *sessionIdentifier in backgroundSessions) {
  1121. GTMSessionFetcher *fetcher = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier];
  1122. if (!fetcher) {
  1123. fetcher = [self fetcherWithSessionIdentifier:sessionIdentifier];
  1124. GTMSESSION_ASSERT_DEBUG(fetcher != nil,
  1125. @"Unexpected invalid session identifier: %@", sessionIdentifier);
  1126. [fetcher beginFetchWithCompletionHandler:nil];
  1127. }
  1128. GTM_LOG_BACKGROUND_SESSION(@"%@ restoring session %@ by creating fetcher %@ %p",
  1129. [self class], sessionIdentifier, fetcher, fetcher);
  1130. if (fetcher != nil) {
  1131. [fetchers addObject:fetcher];
  1132. }
  1133. }
  1134. return fetchers;
  1135. }
  1136. #if TARGET_OS_IPHONE
  1137. + (void)application:(UIApplication *)application
  1138. handleEventsForBackgroundURLSession:(NSString *)identifier
  1139. completionHandler:(GTMSessionFetcherSystemCompletionHandler)completionHandler {
  1140. GTMSessionFetcher *fetcher = [self fetcherWithSessionIdentifier:identifier];
  1141. if (fetcher != nil) {
  1142. fetcher.systemCompletionHandler = completionHandler;
  1143. } else {
  1144. GTM_LOG_BACKGROUND_SESSION(@"%@ did not create background session identifier: %@",
  1145. [self class], identifier);
  1146. }
  1147. }
  1148. #endif
  1149. - (NSString * GTM_NULLABLE_TYPE)sessionIdentifier {
  1150. @synchronized(self) {
  1151. GTMSessionMonitorSynchronized(self);
  1152. return _sessionIdentifier;
  1153. } // @synchronized(self)
  1154. }
  1155. - (void)setSessionIdentifier:(NSString *)sessionIdentifier {
  1156. GTMSESSION_ASSERT_DEBUG(sessionIdentifier != nil, @"Invalid session identifier");
  1157. @synchronized(self) {
  1158. GTMSessionMonitorSynchronized(self);
  1159. GTMSESSION_ASSERT_DEBUG(!_session, @"Unable to set session identifier after session created");
  1160. _sessionIdentifier = [sessionIdentifier copy];
  1161. _usingBackgroundSession = YES;
  1162. _canShareSession = NO;
  1163. [self restoreDefaultStateForSessionIdentifierMetadata];
  1164. } // @synchronized(self)
  1165. }
  1166. - (void)setSessionIdentifierInternal:(GTM_NULLABLE NSString *)sessionIdentifier {
  1167. // This internal method only does a synchronized set of the session identifier.
  1168. // It does not have side effects on the background session, shared session, or
  1169. // session identifier metadata.
  1170. @synchronized(self) {
  1171. GTMSessionMonitorSynchronized(self);
  1172. _sessionIdentifier = [sessionIdentifier copy];
  1173. } // @synchronized(self)
  1174. }
  1175. - (NSDictionary * GTM_NULLABLE_TYPE)sessionUserInfo {
  1176. @synchronized(self) {
  1177. GTMSessionMonitorSynchronized(self);
  1178. if (_sessionUserInfo == nil) {
  1179. // We'll return the metadata dictionary with internal keys removed. This avoids the user
  1180. // re-using the userInfo dictionary later and accidentally including the internal keys.
  1181. NSMutableDictionary *metadata = [[self sessionIdentifierMetadataUnsynchronized] mutableCopy];
  1182. NSSet *keysToRemove = [metadata keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
  1183. return [key hasPrefix:@"_"];
  1184. }];
  1185. [metadata removeObjectsForKeys:[keysToRemove allObjects]];
  1186. if (metadata.count > 0) {
  1187. _sessionUserInfo = metadata;
  1188. }
  1189. }
  1190. return _sessionUserInfo;
  1191. } // @synchronized(self)
  1192. }
  1193. - (void)setSessionUserInfo:(NSDictionary * GTM_NULLABLE_TYPE)dictionary {
  1194. @synchronized(self) {
  1195. GTMSessionMonitorSynchronized(self);
  1196. GTMSESSION_ASSERT_DEBUG(_sessionIdentifier == nil, @"Too late to assign userInfo");
  1197. _sessionUserInfo = dictionary;
  1198. } // @synchronized(self)
  1199. }
  1200. - (GTM_NULLABLE NSDictionary *)sessionIdentifierDefaultMetadata {
  1201. GTMSessionCheckSynchronized(self);
  1202. NSMutableDictionary *defaultUserInfo = [[NSMutableDictionary alloc] init];
  1203. if (_destinationFileURL) {
  1204. defaultUserInfo[kGTMSessionIdentifierDestinationFileURLMetadataKey] =
  1205. [_destinationFileURL absoluteString];
  1206. }
  1207. if (_bodyFileURL) {
  1208. defaultUserInfo[kGTMSessionIdentifierBodyFileURLMetadataKey] = [_bodyFileURL absoluteString];
  1209. }
  1210. return (defaultUserInfo.count > 0) ? defaultUserInfo : nil;
  1211. }
  1212. - (void)restoreDefaultStateForSessionIdentifierMetadata {
  1213. GTMSessionCheckSynchronized(self);
  1214. NSDictionary *metadata = [self sessionIdentifierMetadataUnsynchronized];
  1215. NSString *destinationFileURLString = metadata[kGTMSessionIdentifierDestinationFileURLMetadataKey];
  1216. if (destinationFileURLString) {
  1217. _destinationFileURL = [NSURL URLWithString:destinationFileURLString];
  1218. GTM_LOG_BACKGROUND_SESSION(@"Restoring destination file URL: %@", _destinationFileURL);
  1219. }
  1220. NSString *bodyFileURLString = metadata[kGTMSessionIdentifierBodyFileURLMetadataKey];
  1221. if (bodyFileURLString) {
  1222. _bodyFileURL = [NSURL URLWithString:bodyFileURLString];
  1223. GTM_LOG_BACKGROUND_SESSION(@"Restoring body file URL: %@", _bodyFileURL);
  1224. }
  1225. }
  1226. - (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadata {
  1227. @synchronized(self) {
  1228. GTMSessionMonitorSynchronized(self);
  1229. return [self sessionIdentifierMetadataUnsynchronized];
  1230. }
  1231. }
  1232. - (NSDictionary * GTM_NULLABLE_TYPE)sessionIdentifierMetadataUnsynchronized {
  1233. GTMSessionCheckSynchronized(self);
  1234. // Session Identifier format: "com.google.<ClassName>_<UUID>_<Metadata in JSON format>
  1235. if (!_sessionIdentifier) {
  1236. return nil;
  1237. }
  1238. NSScanner *metadataScanner = [NSScanner scannerWithString:_sessionIdentifier];
  1239. [metadataScanner setCharactersToBeSkipped:nil];
  1240. NSString *metadataString;
  1241. NSString *uuid;
  1242. if ([metadataScanner scanUpToString:@"_" intoString:NULL] &&
  1243. [metadataScanner scanString:@"_" intoString:NULL] &&
  1244. [metadataScanner scanUpToString:@"_" intoString:&uuid] &&
  1245. [metadataScanner scanString:@"_" intoString:NULL] &&
  1246. [metadataScanner scanUpToString:@"\n" intoString:&metadataString]) {
  1247. _sessionIdentifierUUID = uuid;
  1248. NSData *metadataData = [metadataString dataUsingEncoding:NSUTF8StringEncoding];
  1249. NSError *error;
  1250. NSDictionary *metadataDict =
  1251. [NSJSONSerialization JSONObjectWithData:metadataData
  1252. options:0
  1253. error:&error];
  1254. GTM_LOG_BACKGROUND_SESSION(@"User Info from session identifier: %@ %@",
  1255. metadataDict, error ? error : @"");
  1256. return metadataDict;
  1257. }
  1258. return nil;
  1259. }
  1260. - (NSString *)createSessionIdentifierWithMetadata:(NSDictionary * GTM_NULLABLE_TYPE)metadataToInclude {
  1261. NSString *result;
  1262. @synchronized(self) {
  1263. GTMSessionMonitorSynchronized(self);
  1264. // Session Identifier format: "com.google.<ClassName>_<UUID>_<Metadata in JSON format>
  1265. GTMSESSION_ASSERT_DEBUG(!_sessionIdentifier, @"Session identifier already created");
  1266. _sessionIdentifierUUID = [[NSUUID UUID] UUIDString];
  1267. _sessionIdentifier =
  1268. [NSString stringWithFormat:@"%@_%@", kGTMSessionIdentifierPrefix, _sessionIdentifierUUID];
  1269. // Start with user-supplied keys so they cannot accidentally override the fetcher's keys.
  1270. NSMutableDictionary *metadataDict =
  1271. [NSMutableDictionary dictionaryWithDictionary:(NSDictionary * GTM_NONNULL_TYPE)_sessionUserInfo];
  1272. if (metadataToInclude) {
  1273. [metadataDict addEntriesFromDictionary:(NSDictionary *)metadataToInclude];
  1274. }
  1275. NSDictionary *defaultMetadataDict = [self sessionIdentifierDefaultMetadata];
  1276. if (defaultMetadataDict) {
  1277. [metadataDict addEntriesFromDictionary:defaultMetadataDict];
  1278. }
  1279. if (metadataDict.count > 0) {
  1280. NSData *metadataData = [NSJSONSerialization dataWithJSONObject:metadataDict
  1281. options:0
  1282. error:NULL];
  1283. GTMSESSION_ASSERT_DEBUG(metadataData != nil,
  1284. @"Session identifier user info failed to convert to JSON");
  1285. if (metadataData.length > 0) {
  1286. NSString *metadataString = [[NSString alloc] initWithData:metadataData
  1287. encoding:NSUTF8StringEncoding];
  1288. _sessionIdentifier =
  1289. [_sessionIdentifier stringByAppendingFormat:@"_%@", metadataString];
  1290. }
  1291. }
  1292. _didCreateSessionIdentifier = YES;
  1293. result = _sessionIdentifier;
  1294. } // @synchronized(self)
  1295. return result;
  1296. }
  1297. - (void)failToBeginFetchWithError:(NSError *)error {
  1298. @synchronized(self) {
  1299. GTMSessionMonitorSynchronized(self);
  1300. _hasStoppedFetching = YES;
  1301. }
  1302. if (error == nil) {
  1303. error = [NSError errorWithDomain:kGTMSessionFetcherErrorDomain
  1304. code:GTMSessionFetcherErrorDownloadFailed
  1305. userInfo:nil];
  1306. }
  1307. [self invokeFetchCallbacksOnCallbackQueueWithData:nil
  1308. error:error];
  1309. [self releaseCallbacks];
  1310. [_service fetcherDidStop:self];
  1311. self.authorizer = nil;
  1312. }
  1313. + (GTMSessionCookieStorage *)staticCookieStorage {
  1314. static GTMSessionCookieStorage *gCookieStorage = nil;
  1315. static dispatch_once_t onceToken;
  1316. dispatch_once(&onceToken, ^{
  1317. gCookieStorage = [[GTMSessionCookieStorage alloc] init];
  1318. });
  1319. return gCookieStorage;
  1320. }
  1321. #if GTM_BACKGROUND_TASK_FETCHING
  1322. - (void)endBackgroundTask {
  1323. // Whenever the connection stops or background execution expires,
  1324. // we need to tell UIApplication we're done.
  1325. //
  1326. // We'll wait on _callbackGroup to ensure that any callbacks in flight have executed,
  1327. // and that we access backgroundTaskIdentifier on the main thread, as happens when the
  1328. // task has expired.
  1329. UIBackgroundTaskIdentifier bgTaskID = self.backgroundTaskIdentifier;
  1330. if (bgTaskID != UIBackgroundTaskInvalid) {
  1331. self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
  1332. id<GTMUIApplicationProtocol> app = [[self class] fetcherUIApplication];
  1333. [app endBackgroundTask:bgTaskID];
  1334. }
  1335. }
  1336. #endif // GTM_BACKGROUND_TASK_FETCHING
  1337. - (void)authorizeRequest {
  1338. GTMSessionCheckNotSynchronized(self);
  1339. id authorizer = self.authorizer;
  1340. SEL asyncAuthSel = @selector(authorizeRequest:delegate:didFinishSelector:);
  1341. if ([authorizer respondsToSelector:asyncAuthSel]) {
  1342. SEL callbackSel = @selector(authorizer:request:finishedWithError:);
  1343. NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
  1344. [authorizer authorizeRequest:mutableRequest
  1345. delegate:self
  1346. didFinishSelector:callbackSel];
  1347. } else {
  1348. GTMSESSION_ASSERT_DEBUG(authorizer == nil, @"invalid authorizer for fetch");
  1349. // No authorizing possible, and authorizing happens only after any delay;
  1350. // just begin fetching
  1351. [self beginFetchMayDelay:NO
  1352. mayAuthorize:NO];
  1353. }
  1354. }
  1355. - (void)authorizer:(id<GTMFetcherAuthorizationProtocol>)auth
  1356. request:(NSMutableURLRequest *)authorizedRequest
  1357. finishedWithError:(NSError *)error {
  1358. GTMSessionCheckNotSynchronized(self);
  1359. if (error != nil) {
  1360. // We can't fetch without authorization
  1361. [self failToBeginFetchWithError:error];
  1362. } else {
  1363. @synchronized(self) {
  1364. _request = authorizedRequest;
  1365. }
  1366. [self beginFetchMayDelay:NO
  1367. mayAuthorize:NO];
  1368. }
  1369. }
  1370. - (BOOL)canFetchWithBackgroundSession {
  1371. // Subclasses may override.
  1372. return YES;
  1373. }
  1374. // Returns YES if the fetcher has been started and has not yet stopped.
  1375. //
  1376. // Fetching includes waiting for authorization or for retry, waiting to be allowed by the
  1377. // service object to start the request, and actually fetching the request.
  1378. - (BOOL)isFetching {
  1379. @synchronized(self) {
  1380. GTMSessionMonitorSynchronized(self);
  1381. return [self isFetchingUnsynchronized];
  1382. }
  1383. }
  1384. - (BOOL)isFetchingUnsynchronized {
  1385. GTMSessionCheckSynchronized(self);
  1386. BOOL hasBegun = (_initialBeginFetchDate != nil);
  1387. return hasBegun && !_hasStoppedFetching;
  1388. }
  1389. - (NSURLResponse * GTM_NULLABLE_TYPE)response {
  1390. @synchronized(self) {
  1391. GTMSessionMonitorSynchronized(self);
  1392. NSURLResponse *response = [self responseUnsynchronized];
  1393. return response;
  1394. } // @synchronized(self)
  1395. }
  1396. - (NSURLResponse * GTM_NULLABLE_TYPE)responseUnsynchronized {
  1397. GTMSessionCheckSynchronized(self);
  1398. NSURLResponse *response = _sessionTask.response;
  1399. if (!response) response = _response;
  1400. return response;
  1401. }
  1402. - (NSInteger)statusCode {
  1403. @synchronized(self) {
  1404. GTMSessionMonitorSynchronized(self);
  1405. NSInteger statusCode = [self statusCodeUnsynchronized];
  1406. return statusCode;
  1407. } // @synchronized(self)
  1408. }
  1409. - (NSInteger)statusCodeUnsynchronized {
  1410. GTMSessionCheckSynchronized(self);
  1411. NSURLResponse *response = [self responseUnsynchronized];
  1412. NSInteger statusCode;
  1413. if ([response respondsToSelector:@selector(statusCode)]) {
  1414. statusCode = [(NSHTTPURLResponse *)response statusCode];
  1415. } else {
  1416. // Default to zero, in hopes of hinting "Unknown" (we can't be
  1417. // sure that things are OK enough to use 200).
  1418. statusCode = 0;
  1419. }
  1420. return statusCode;
  1421. }
  1422. - (NSDictionary * GTM_NULLABLE_TYPE)responseHeaders {
  1423. GTMSessionCheckNotSynchronized(self);
  1424. NSURLResponse *response = self.response;
  1425. if ([response respondsToSelector:@selector(allHeaderFields)]) {
  1426. NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
  1427. return headers;
  1428. }
  1429. return nil;
  1430. }
  1431. - (NSDictionary * GTM_NULLABLE_TYPE)responseHeadersUnsynchronized {
  1432. GTMSessionCheckSynchronized(self);
  1433. NSURLResponse *response = [self responseUnsynchronized];
  1434. if ([response respondsToSelector:@selector(allHeaderFields)]) {
  1435. NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
  1436. return headers;
  1437. }
  1438. return nil;
  1439. }
  1440. - (void)releaseCallbacks {
  1441. // Avoid releasing blocks in the sync section since objects dealloc'd by
  1442. // the blocks being released may call back into the fetcher or fetcher
  1443. // service.
  1444. dispatch_queue_t NS_VALID_UNTIL_END_OF_SCOPE holdCallbackQueue;
  1445. GTMSessionFetcherCompletionHandler NS_VALID_UNTIL_END_OF_SCOPE holdCompletionHandler;
  1446. @synchronized(self) {
  1447. GTMSessionMonitorSynchronized(self);
  1448. holdCallbackQueue = _callbackQueue;
  1449. holdCompletionHandler = _completionHandler;
  1450. _callbackQueue = nil;
  1451. _completionHandler = nil; // Setter overridden in upload. Setter assumed to be used externally.
  1452. }
  1453. // Set local callback pointers to nil here rather than let them release at the end of the scope
  1454. // to make any problems due to the blocks being released be a bit more obvious in a stack trace.
  1455. holdCallbackQueue = nil;
  1456. holdCompletionHandler = nil;
  1457. self.configurationBlock = nil;
  1458. self.didReceiveResponseBlock = nil;
  1459. self.challengeBlock = nil;
  1460. self.willRedirectBlock = nil;
  1461. self.sendProgressBlock = nil;
  1462. self.receivedProgressBlock = nil;
  1463. self.downloadProgressBlock = nil;
  1464. self.accumulateDataBlock = nil;
  1465. self.willCacheURLResponseBlock = nil;
  1466. self.retryBlock = nil;
  1467. self.testBlock = nil;
  1468. self.resumeDataBlock = nil;
  1469. }
  1470. - (void)forgetSessionIdentifierForFetcher {
  1471. GTMSessionCheckSynchronized(self);
  1472. [self forgetSessionIdentifierForFetcherWithoutSyncCheck];
  1473. }
  1474. - (void)forgetSessionIdentifierForFetcherWithoutSyncCheck {
  1475. // This should be called inside a @synchronized block (except during dealloc.)
  1476. if (_sessionIdentifier) {
  1477. NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIdentifierToFetcherMap];
  1478. [sessionIdentifierToFetcherMap removeObjectForKey:_sessionIdentifier];
  1479. _sessionIdentifier = nil;
  1480. _didCreateSessionIdentifier = NO;
  1481. }
  1482. }
  1483. // External stop method
  1484. - (void)stopFetching {
  1485. @synchronized(self) {
  1486. GTMSessionMonitorSynchronized(self);
  1487. // Prevent enqueued callbacks from executing.
  1488. _userStoppedFetching = YES;
  1489. } // @synchronized(self)
  1490. [self stopFetchReleasingCallbacks:YES];
  1491. }
  1492. // Cancel the fetch of the URL that's currently in progress.
  1493. //
  1494. // If shouldReleaseCallbacks is NO then the fetch will be retried so the callbacks
  1495. // need to still be retained.
  1496. - (void)stopFetchReleasingCallbacks:(BOOL)shouldReleaseCallbacks {
  1497. [self removePersistedBackgroundSessionFromDefaults];
  1498. id<GTMSessionFetcherServiceProtocol> service;
  1499. NSMutableURLRequest *request;
  1500. // If the task or the retry timer is all that's retaining the fetcher,
  1501. // we want to be sure this instance survives stopping at least long enough for
  1502. // the stack to unwind.
  1503. __autoreleasing GTMSessionFetcher *holdSelf = self;
  1504. BOOL hasCanceledTask = NO;
  1505. [holdSelf destroyRetryTimer];
  1506. @synchronized(self) {
  1507. GTMSessionMonitorSynchronized(self);
  1508. _hasStoppedFetching = YES;
  1509. service = _service;
  1510. request = _request;
  1511. if (_sessionTask) {
  1512. // In case cancelling the task or session calls this recursively, we want
  1513. // to ensure that we'll only release the task and delegate once,
  1514. // so first set _sessionTask to nil
  1515. //
  1516. // This may be called in a callback from the task, so use autorelease to avoid
  1517. // releasing the task in its own callback.
  1518. __autoreleasing NSURLSessionTask *oldTask = _sessionTask;
  1519. if (!_isUsingTestBlock) {
  1520. _response = _sessionTask.response;
  1521. }
  1522. _sessionTask = nil;
  1523. if ([oldTask state] != NSURLSessionTaskStateCompleted) {
  1524. // For download tasks, when the fetch is stopped, we may provide resume data that can
  1525. // be used to create a new session.
  1526. BOOL mayResume = (_resumeDataBlock
  1527. && [oldTask respondsToSelector:@selector(cancelByProducingResumeData:)]);
  1528. if (!mayResume) {
  1529. [oldTask cancel];
  1530. // A side effect of stopping the task is that URLSession:task:didCompleteWithError:
  1531. // will be invoked asynchronously on the delegate queue.
  1532. } else {
  1533. void (^resumeBlock)(NSData *) = _resumeDataBlock;
  1534. _resumeDataBlock = nil;
  1535. // Save callbackQueue since releaseCallbacks clears it.
  1536. dispatch_queue_t callbackQueue = _callbackQueue;
  1537. dispatch_group_enter(_callbackGroup);
  1538. [(NSURLSessionDownloadTask *)oldTask cancelByProducingResumeData:^(NSData *resumeData) {
  1539. [self invokeOnCallbackQueue:callbackQueue
  1540. afterUserStopped:YES
  1541. block:^{
  1542. resumeBlock(resumeData);
  1543. dispatch_group_leave(_callbackGroup);
  1544. }];
  1545. }];
  1546. }
  1547. hasCanceledTask = YES;
  1548. }
  1549. }
  1550. // If the task was canceled, wait until the URLSession:task:didCompleteWithError: to call
  1551. // finishTasksAndInvalidate, since calling it immediately tends to crash, see radar 18471901.
  1552. if (_session) {
  1553. BOOL shouldInvalidate = _shouldInvalidateSession;
  1554. #if TARGET_OS_IPHONE
  1555. // Don't invalidate if we've got a systemCompletionHandler, since
  1556. // URLSessionDidFinishEventsForBackgroundURLSession: won't be called if invalidated.
  1557. shouldInvalidate = shouldInvalidate && !self.systemCompletionHandler;
  1558. #endif
  1559. if (shouldInvalidate) {
  1560. __autoreleasing NSURLSession *oldSession = _session;
  1561. _session = nil;
  1562. if (!hasCanceledTask) {
  1563. [oldSession finishTasksAndInvalidate];
  1564. } else {
  1565. _sessionNeedingInvalidation = oldSession;
  1566. }
  1567. }
  1568. }
  1569. } // @synchronized(self)
  1570. // send the stopped notification
  1571. [self sendStopNotificationIfNeeded];
  1572. [_authorizer stopAuthorizationForRequest:request];
  1573. if (shouldReleaseCallbacks) {
  1574. [self releaseCallbacks];
  1575. self.authorizer = nil;
  1576. }
  1577. [service fetcherDidStop:self];
  1578. #if GTM_BACKGROUND_TASK_FETCHING
  1579. [self endBackgroundTask];
  1580. #endif
  1581. }
  1582. - (void)setStopNotificationNeeded:(BOOL)flag {
  1583. @synchronized(self) {
  1584. GTMSessionMonitorSynchronized(self);
  1585. _isStopNotificationNeeded = flag;
  1586. } // @synchronized(self)
  1587. }
  1588. - (void)sendStopNotificationIfNeeded {
  1589. BOOL sendNow = NO;
  1590. @synchronized(self) {
  1591. GTMSessionMonitorSynchronized(self);
  1592. if (_isStopNotificationNeeded) {
  1593. _isStopNotificationNeeded = NO;
  1594. sendNow = YES;
  1595. }
  1596. } // @synchronized(self)
  1597. if (sendNow) {
  1598. [self postNotificationOnMainThreadWithName:kGTMSessionFetcherStoppedNotification
  1599. userInfo:nil
  1600. requireAsync:NO];
  1601. }
  1602. }
  1603. - (void)retryFetch {
  1604. [self stopFetchReleasingCallbacks:NO];
  1605. GTMSessionFetcherCompletionHandler completionHandler;
  1606. // A retry will need a configuration with a fresh session identifier.
  1607. @synchronized(self) {
  1608. GTMSessionMonitorSynchronized(self);
  1609. if (_sessionIdentifier && _didCreateSessionIdentifier) {
  1610. [self forgetSessionIdentifierForFetcher];
  1611. _configuration = nil;
  1612. }
  1613. if (_canShareSession) {
  1614. // Force a grab of the current session from the fetcher service in case
  1615. // the service's old one has become invalid.
  1616. _session = nil;
  1617. }
  1618. completionHandler = _completionHandler;
  1619. } // @synchronized(self)
  1620. [self beginFetchWithCompletionHandler:completionHandler];
  1621. }
  1622. - (BOOL)waitForCompletionWithTimeout:(NSTimeInterval)timeoutInSeconds {
  1623. // Uncovered in upload fetcher testing, because the chunk fetcher is being waited on, and gets
  1624. // released by the upload code. The uploader just holds onto it with an ivar, and that gets
  1625. // nilled in the chunk fetcher callback.
  1626. // Used once in while loop just to avoid unused variable compiler warning.
  1627. __autoreleasing GTMSessionFetcher *holdSelf = self;
  1628. NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  1629. BOOL shouldSpinRunLoop = ([NSThread isMainThread] &&
  1630. (!self.callbackQueue
  1631. || self.callbackQueue == dispatch_get_main_queue()));
  1632. BOOL expired = NO;
  1633. // Loop until the callbacks have been called and released, and until
  1634. // the connection is no longer pending, until there are no callback dispatches
  1635. // in flight, or until the timeout has expired.
  1636. int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms
  1637. while (1) {
  1638. BOOL isTaskInProgress = (holdSelf->_sessionTask
  1639. && [_sessionTask state] != NSURLSessionTaskStateCompleted);
  1640. BOOL needsToCallCompletion = (_completionHandler != nil);
  1641. BOOL isCallbackInProgress = (_callbackGroup
  1642. && dispatch_group_wait(_callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta)));
  1643. if (!isTaskInProgress && !needsToCallCompletion && !isCallbackInProgress) break;
  1644. expired = ([giveUpDate timeIntervalSinceNow] < 0);
  1645. if (expired) {
  1646. GTMSESSION_LOG_DEBUG(@"GTMSessionFetcher waitForCompletionWithTimeout:%0.1f expired -- "
  1647. @"%@%@%@", timeoutInSeconds,
  1648. isTaskInProgress ? @"taskInProgress " : @"",
  1649. needsToCallCompletion ? @"needsToCallCompletion " : @"",
  1650. isCallbackInProgress ? @"isCallbackInProgress" : @"");
  1651. break;
  1652. }
  1653. // Run the current run loop 1/1000 of a second to give the networking
  1654. // code a chance to work
  1655. const NSTimeInterval kSpinInterval = 0.001;
  1656. if (shouldSpinRunLoop) {
  1657. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval];
  1658. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  1659. } else {
  1660. [NSThread sleepForTimeInterval:kSpinInterval];
  1661. }
  1662. }
  1663. return !expired;
  1664. }
  1665. + (void)setGlobalTestBlock:(GTMSessionFetcherTestBlock GTM_NULLABLE_TYPE)block {
  1666. #if GTM_DISABLE_FETCHER_TEST_BLOCK
  1667. GTMSESSION_ASSERT_DEBUG(block == nil, @"test blocks disabled");
  1668. #endif
  1669. gGlobalTestBlock = [block copy];
  1670. }
  1671. #if TARGET_OS_IPHONE
  1672. static GTM_NULLABLE_TYPE id<GTMUIApplicationProtocol> gSubstituteUIApp;
  1673. + (void)setSubstituteUIApplication:(nullable id<GTMUIApplicationProtocol>)app {
  1674. gSubstituteUIApp = app;
  1675. }
  1676. + (nullable id<GTMUIApplicationProtocol>)substituteUIApplication {
  1677. return gSubstituteUIApp;
  1678. }
  1679. + (nullable id<GTMUIApplicationProtocol>)fetcherUIApplication {
  1680. id<GTMUIApplicationProtocol> app = gSubstituteUIApp;
  1681. if (app) return app;
  1682. // Some projects use GTM_BACKGROUND_TASK_FETCHING to avoid compile-time references
  1683. // to UIApplication.
  1684. #if GTM_BACKGROUND_TASK_FETCHING
  1685. return (id<GTMUIApplicationProtocol>) [UIApplication sharedApplication];
  1686. #else
  1687. return nil;
  1688. #endif
  1689. }
  1690. #endif // TARGET_OS_IPHONE
  1691. #pragma mark NSURLSession Delegate Methods
  1692. // NSURLSession documentation indicates that redirectRequest can be passed to the handler
  1693. // but empirically redirectRequest lacks the HTTP body, so passing it will break POSTs.
  1694. // Instead, we construct a new request, a copy of the original, with overrides from the
  1695. // redirect.
  1696. - (void)URLSession:(NSURLSession *)session
  1697. task:(NSURLSessionTask *)task
  1698. willPerformHTTPRedirection:(NSHTTPURLResponse *)redirectResponse
  1699. newRequest:(NSURLRequest *)redirectRequest
  1700. completionHandler:(void (^)(NSURLRequest * GTM_NULLABLE_TYPE))handler {
  1701. [self setSessionTask:task];
  1702. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ willPerformHTTPRedirection:%@ newRequest:%@",
  1703. [self class], self, session, task, redirectResponse, redirectRequest);
  1704. if ([self userStoppedFetching]) {
  1705. handler(nil);
  1706. return;
  1707. }
  1708. if (redirectRequest && redirectResponse) {
  1709. // Copy the original request, including the body.
  1710. NSURLRequest *originalRequest = self.request;
  1711. NSMutableURLRequest *newRequest = [originalRequest mutableCopy];
  1712. // Disallow scheme changes (say, from https to http).
  1713. NSURL *originalRequestURL = originalRequest.URL;
  1714. NSURL *redirectRequestURL = redirectRequest.URL;
  1715. NSString *originalScheme = originalRequestURL.scheme;
  1716. NSString *redirectScheme = redirectRequestURL.scheme;
  1717. if (originalScheme != nil
  1718. && [originalScheme caseInsensitiveCompare:@"http"] == NSOrderedSame
  1719. && redirectScheme != nil
  1720. && [redirectScheme caseInsensitiveCompare:@"https"] == NSOrderedSame) {
  1721. // Allow the change from http to https.
  1722. } else {
  1723. // Disallow any other scheme changes.
  1724. redirectScheme = originalScheme;
  1725. }
  1726. // The new requests's URL overrides the original's URL.
  1727. NSURLComponents *components = [NSURLComponents componentsWithURL:redirectRequestURL
  1728. resolvingAgainstBaseURL:NO];
  1729. components.scheme = redirectScheme;
  1730. NSURL *newURL = components.URL;
  1731. [newRequest setURL:newURL];
  1732. // Any headers in the redirect override headers in the original.
  1733. NSDictionary *redirectHeaders = redirectRequest.allHTTPHeaderFields;
  1734. for (NSString *key in redirectHeaders) {
  1735. NSString *value = [redirectHeaders objectForKey:key];
  1736. [newRequest setValue:value forHTTPHeaderField:key];
  1737. }
  1738. redirectRequest = newRequest;
  1739. // Log the response we just received
  1740. [self setResponse:redirectResponse];
  1741. [self logNowWithError:nil];
  1742. GTMSessionFetcherWillRedirectBlock willRedirectBlock = self.willRedirectBlock;
  1743. if (willRedirectBlock) {
  1744. @synchronized(self) {
  1745. GTMSessionMonitorSynchronized(self);
  1746. [self invokeOnCallbackQueueAfterUserStopped:YES
  1747. block:^{
  1748. willRedirectBlock(redirectResponse, redirectRequest, ^(NSURLRequest *clientRequest) {
  1749. // Update the request for future logging.
  1750. [self updateMutableRequest:[clientRequest mutableCopy]];
  1751. handler(clientRequest);
  1752. });
  1753. }];
  1754. } // @synchronized(self)
  1755. return;
  1756. }
  1757. // Continues here if the client did not provide a redirect block.
  1758. // Update the request for future logging.
  1759. [self updateMutableRequest:[redirectRequest mutableCopy]];
  1760. }
  1761. handler(redirectRequest);
  1762. }
  1763. - (void)URLSession:(NSURLSession *)session
  1764. dataTask:(NSURLSessionDataTask *)dataTask
  1765. didReceiveResponse:(NSURLResponse *)response
  1766. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))handler {
  1767. [self setSessionTask:dataTask];
  1768. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveResponse:%@",
  1769. [self class], self, session, dataTask, response);
  1770. void (^accumulateAndFinish)(NSURLSessionResponseDisposition) =
  1771. ^(NSURLSessionResponseDisposition dispositionValue) {
  1772. // This method is called when the server has determined that it
  1773. // has enough information to create the NSURLResponse
  1774. // it can be called multiple times, for example in the case of a
  1775. // redirect, so each time we reset the data.
  1776. @synchronized(self) {
  1777. GTMSessionMonitorSynchronized(self);
  1778. BOOL hadPreviousData = _downloadedLength > 0;
  1779. [_downloadedData setLength:0];
  1780. _downloadedLength = 0;
  1781. if (hadPreviousData && (dispositionValue != NSURLSessionResponseCancel)) {
  1782. // Tell the accumulate block to discard prior data.
  1783. GTMSessionFetcherAccumulateDataBlock accumulateBlock = _accumulateDataBlock;
  1784. if (accumulateBlock) {
  1785. [self invokeOnCallbackQueueUnlessStopped:^{
  1786. accumulateBlock(nil);
  1787. }];
  1788. }
  1789. }
  1790. } // @synchronized(self)
  1791. handler(dispositionValue);
  1792. };
  1793. GTMSessionFetcherDidReceiveResponseBlock receivedResponseBlock;
  1794. @synchronized(self) {
  1795. GTMSessionMonitorSynchronized(self);
  1796. receivedResponseBlock = _didReceiveResponseBlock;
  1797. if (receivedResponseBlock) {
  1798. // We will ultimately need to call back to NSURLSession's handler with the disposition value
  1799. // for this delegate method even if the user has stopped the fetcher.
  1800. [self invokeOnCallbackQueueAfterUserStopped:YES
  1801. block:^{
  1802. receivedResponseBlock(response, ^(NSURLSessionResponseDisposition desiredDisposition) {
  1803. accumulateAndFinish(desiredDisposition);
  1804. });
  1805. }];
  1806. }
  1807. } // @synchronized(self)
  1808. if (receivedResponseBlock == nil) {
  1809. accumulateAndFinish(NSURLSessionResponseAllow);
  1810. }
  1811. }
  1812. - (void)URLSession:(NSURLSession *)session
  1813. dataTask:(NSURLSessionDataTask *)dataTask
  1814. didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
  1815. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didBecomeDownloadTask:%@",
  1816. [self class], self, session, dataTask, downloadTask);
  1817. [self setSessionTask:downloadTask];
  1818. }
  1819. - (void)URLSession:(NSURLSession *)session
  1820. task:(NSURLSessionTask *)task
  1821. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  1822. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
  1823. NSURLCredential * GTM_NULLABLE_TYPE credential))handler {
  1824. [self setSessionTask:task];
  1825. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didReceiveChallenge:%@",
  1826. [self class], self, session, task, challenge);
  1827. GTMSessionFetcherChallengeBlock challengeBlock = self.challengeBlock;
  1828. if (challengeBlock) {
  1829. // The fetcher user has provided custom challenge handling.
  1830. //
  1831. // We will ultimately need to call back to NSURLSession's handler with the disposition value
  1832. // for this delegate method even if the user has stopped the fetcher.
  1833. @synchronized(self) {
  1834. GTMSessionMonitorSynchronized(self);
  1835. [self invokeOnCallbackQueueAfterUserStopped:YES
  1836. block:^{
  1837. challengeBlock(self, challenge, handler);
  1838. }];
  1839. }
  1840. } else {
  1841. // No challenge block was provided by the client.
  1842. [self respondToChallenge:challenge
  1843. completionHandler:handler];
  1844. }
  1845. }
  1846. - (void)respondToChallenge:(NSURLAuthenticationChallenge *)challenge
  1847. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
  1848. NSURLCredential * GTM_NULLABLE_TYPE credential))handler {
  1849. @synchronized(self) {
  1850. GTMSessionMonitorSynchronized(self);
  1851. NSInteger previousFailureCount = [challenge previousFailureCount];
  1852. if (previousFailureCount <= 2) {
  1853. NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
  1854. NSString *authenticationMethod = [protectionSpace authenticationMethod];
  1855. if ([authenticationMethod isEqual:NSURLAuthenticationMethodServerTrust]) {
  1856. // SSL.
  1857. //
  1858. // Background sessions seem to require an explicit check of the server trust object
  1859. // rather than default handling.
  1860. SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
  1861. if (serverTrust == NULL) {
  1862. // No server trust information is available.
  1863. handler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  1864. } else {
  1865. // Server trust information is available.
  1866. void (^callback)(SecTrustRef, BOOL) = ^(SecTrustRef trustRef, BOOL allow){
  1867. if (allow) {
  1868. NSURLCredential *trustCredential = [NSURLCredential credentialForTrust:trustRef];
  1869. handler(NSURLSessionAuthChallengeUseCredential, trustCredential);
  1870. } else {
  1871. GTMSESSION_LOG_DEBUG(@"Cancelling authentication challenge for %@", _request.URL);
  1872. handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  1873. }
  1874. };
  1875. if (_allowInvalidServerCertificates) {
  1876. callback(serverTrust, YES);
  1877. } else {
  1878. [[self class] evaluateServerTrust:serverTrust
  1879. forRequest:_request
  1880. completionHandler:callback];
  1881. }
  1882. }
  1883. return;
  1884. }
  1885. NSURLCredential *credential = _credential;
  1886. if ([[challenge protectionSpace] isProxy] && _proxyCredential != nil) {
  1887. credential = _proxyCredential;
  1888. }
  1889. if (credential) {
  1890. handler(NSURLSessionAuthChallengeUseCredential, credential);
  1891. } else {
  1892. // The credential is still nil; tell the OS to use the default handling. This is needed
  1893. // for things that can come out of the keychain (proxies, client certificates, etc.).
  1894. //
  1895. // Note: Looking up a credential with NSURLCredentialStorage's
  1896. // defaultCredentialForProtectionSpace: is *not* the same invoking the handler with
  1897. // NSURLSessionAuthChallengePerformDefaultHandling. In the case of
  1898. // NSURLAuthenticationMethodClientCertificate, you can get nil back from
  1899. // NSURLCredentialStorage, while using this code path instead works.
  1900. handler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  1901. }
  1902. } else {
  1903. // We've failed auth 3 times. The completion handler will be called with code
  1904. // NSURLErrorCancelled.
  1905. handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  1906. }
  1907. } // @synchronized(self)
  1908. }
  1909. // Validate the certificate chain.
  1910. //
  1911. // This may become a public method if it appears to be useful to users.
  1912. + (void)evaluateServerTrust:(SecTrustRef)serverTrust
  1913. forRequest:(NSURLRequest *)request
  1914. completionHandler:(void (^)(SecTrustRef trustRef, BOOL allow))handler {
  1915. // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7.
  1916. CFRetain(serverTrust);
  1917. // Evaluate the certificate chain.
  1918. //
  1919. // The delegate queue may be the main thread. Trust evaluation could cause some
  1920. // blocking network activity, so we must evaluate async, as documented at
  1921. // https://developer.apple.com/library/ios/technotes/tn2232/
  1922. //
  1923. // We must also avoid multiple uses of the trust object, per docs:
  1924. // "It is not safe to call this function concurrently with any other function that uses
  1925. // the same trust management object, or to re-enter this function for the same trust
  1926. // management object."
  1927. //
  1928. // SecTrustEvaluateAsync both does sync execution of Evaluate and calls back on the
  1929. // queue passed to it, according to at sources in
  1930. // http://www.opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.9/lib/SecTrust.cpp
  1931. // It would require a global serial queue to ensure the evaluate happens only on a
  1932. // single thread at a time, so we'll stick with using SecTrustEvaluate on a background
  1933. // thread.
  1934. dispatch_queue_t evaluateBackgroundQueue =
  1935. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  1936. dispatch_async(evaluateBackgroundQueue, ^{
  1937. // It looks like the implementation of SecTrustEvaluate() on Mac grabs a global lock,
  1938. // so it may be redundant for us to also lock, but it's easy to synchronize here
  1939. // anyway.
  1940. SecTrustResultType trustEval = kSecTrustResultInvalid;
  1941. BOOL shouldAllow;
  1942. OSStatus trustError;
  1943. @synchronized([GTMSessionFetcher class]) {
  1944. GTMSessionMonitorSynchronized([GTMSessionFetcher class]);
  1945. trustError = SecTrustEvaluate(serverTrust, &trustEval);
  1946. }
  1947. if (trustError != errSecSuccess) {
  1948. GTMSESSION_LOG_DEBUG(@"Error %d evaluating trust for %@",
  1949. (int)trustError, request);
  1950. shouldAllow = NO;
  1951. } else {
  1952. // Having a trust level "unspecified" by the user is the usual result, described at
  1953. // https://developer.apple.com/library/mac/qa/qa1360
  1954. if (trustEval == kSecTrustResultUnspecified
  1955. || trustEval == kSecTrustResultProceed) {
  1956. shouldAllow = YES;
  1957. } else {
  1958. shouldAllow = NO;
  1959. GTMSESSION_LOG_DEBUG(@"Challenge SecTrustResultType %u for %@, properties: %@",
  1960. trustEval, request.URL.host,
  1961. CFBridgingRelease(SecTrustCopyProperties(serverTrust)));
  1962. }
  1963. }
  1964. handler(serverTrust, shouldAllow);
  1965. CFRelease(serverTrust);
  1966. });
  1967. }
  1968. - (void)invokeOnCallbackQueueUnlessStopped:(void (^)(void))block {
  1969. [self invokeOnCallbackQueueAfterUserStopped:NO
  1970. block:block];
  1971. }
  1972. - (void)invokeOnCallbackQueueAfterUserStopped:(BOOL)afterStopped
  1973. block:(void (^)(void))block {
  1974. GTMSessionCheckSynchronized(self);
  1975. [self invokeOnCallbackUnsynchronizedQueueAfterUserStopped:afterStopped
  1976. block:block];
  1977. }
  1978. - (void)invokeOnCallbackUnsynchronizedQueueAfterUserStopped:(BOOL)afterStopped
  1979. block:(void (^)(void))block {
  1980. // testBlock simulation code may not be synchronizing when this is invoked.
  1981. [self invokeOnCallbackQueue:_callbackQueue
  1982. afterUserStopped:afterStopped
  1983. block:block];
  1984. }
  1985. - (void)invokeOnCallbackQueue:(dispatch_queue_t)callbackQueue
  1986. afterUserStopped:(BOOL)afterStopped
  1987. block:(void (^)(void))block {
  1988. if (callbackQueue) {
  1989. dispatch_group_async(_callbackGroup, callbackQueue, ^{
  1990. if (!afterStopped) {
  1991. NSDate *serviceStoppedAllDate = [_service stoppedAllFetchersDate];
  1992. @synchronized(self) {
  1993. GTMSessionMonitorSynchronized(self);
  1994. // Avoid a race between stopFetching and the callback.
  1995. if (_userStoppedFetching) {
  1996. return;
  1997. }
  1998. // Also avoid calling back if the service has stopped all fetchers
  1999. // since this one was created. The fetcher may have stopped before
  2000. // stopAllFetchers was invoked, so _userStoppedFetching wasn't set,
  2001. // but the app still won't expect the callback to fire after
  2002. // the service's stopAllFetchers was invoked.
  2003. if (serviceStoppedAllDate
  2004. && [_initialBeginFetchDate compare:serviceStoppedAllDate] != NSOrderedDescending) {
  2005. // stopAllFetchers was called after this fetcher began.
  2006. return;
  2007. }
  2008. } // @synchronized(self)
  2009. }
  2010. block();
  2011. });
  2012. }
  2013. }
  2014. - (void)invokeFetchCallbacksOnCallbackQueueWithData:(GTM_NULLABLE NSData *)data
  2015. error:(GTM_NULLABLE NSError *)error {
  2016. // Callbacks will be released in the method stopFetchReleasingCallbacks:
  2017. GTMSessionFetcherCompletionHandler handler;
  2018. @synchronized(self) {
  2019. GTMSessionMonitorSynchronized(self);
  2020. handler = _completionHandler;
  2021. if (handler) {
  2022. [self invokeOnCallbackQueueUnlessStopped:^{
  2023. handler(data, error);
  2024. // Post a notification, primarily to allow code to collect responses for
  2025. // testing.
  2026. //
  2027. // The observing code is not likely on the fetcher's callback
  2028. // queue, so this posts explicitly to the main queue.
  2029. NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
  2030. if (data) {
  2031. userInfo[kGTMSessionFetcherCompletionDataKey] = data;
  2032. }
  2033. if (error) {
  2034. userInfo[kGTMSessionFetcherCompletionErrorKey] = error;
  2035. }
  2036. [self postNotificationOnMainThreadWithName:kGTMSessionFetcherCompletionInvokedNotification
  2037. userInfo:userInfo
  2038. requireAsync:NO];
  2039. }];
  2040. }
  2041. } // @synchronized(self)
  2042. }
  2043. - (void)postNotificationOnMainThreadWithName:(NSString *)noteName
  2044. userInfo:(GTM_NULLABLE NSDictionary *)userInfo
  2045. requireAsync:(BOOL)requireAsync {
  2046. dispatch_block_t postBlock = ^{
  2047. [[NSNotificationCenter defaultCenter] postNotificationName:noteName
  2048. object:self
  2049. userInfo:userInfo];
  2050. };
  2051. if ([NSThread isMainThread] && !requireAsync) {
  2052. // Post synchronously for compatibility with older code using the fetcher.
  2053. // Avoid calling out to other code from inside a sync block to avoid risk
  2054. // of a deadlock or of recursive sync.
  2055. GTMSessionCheckNotSynchronized(self);
  2056. postBlock();
  2057. } else {
  2058. dispatch_async(dispatch_get_main_queue(), postBlock);
  2059. }
  2060. }
  2061. - (void)URLSession:(NSURLSession *)session
  2062. task:(NSURLSessionTask *)uploadTask
  2063. needNewBodyStream:(void (^)(NSInputStream * GTM_NULLABLE_TYPE bodyStream))completionHandler {
  2064. [self setSessionTask:uploadTask];
  2065. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ needNewBodyStream:",
  2066. [self class], self, session, uploadTask);
  2067. @synchronized(self) {
  2068. GTMSessionMonitorSynchronized(self);
  2069. GTMSessionFetcherBodyStreamProvider provider = _bodyStreamProvider;
  2070. #if !STRIP_GTM_FETCH_LOGGING
  2071. if ([self respondsToSelector:@selector(loggedStreamProviderForStreamProvider:)]) {
  2072. provider = [self performSelector:@selector(loggedStreamProviderForStreamProvider:)
  2073. withObject:provider];
  2074. }
  2075. #endif
  2076. if (provider) {
  2077. [self invokeOnCallbackQueueUnlessStopped:^{
  2078. provider(completionHandler);
  2079. }];
  2080. } else {
  2081. GTMSESSION_ASSERT_DEBUG(NO, @"NSURLSession expects a stream provider");
  2082. completionHandler(nil);
  2083. }
  2084. } // @synchronized(self)
  2085. }
  2086. - (void)URLSession:(NSURLSession *)session
  2087. task:(NSURLSessionTask *)task
  2088. didSendBodyData:(int64_t)bytesSent
  2089. totalBytesSent:(int64_t)totalBytesSent
  2090. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
  2091. [self setSessionTask:task];
  2092. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didSendBodyData:%lld"
  2093. @" totalBytesSent:%lld totalBytesExpectedToSend:%lld",
  2094. [self class], self, session, task, bytesSent, totalBytesSent,
  2095. totalBytesExpectedToSend);
  2096. @synchronized(self) {
  2097. GTMSessionMonitorSynchronized(self);
  2098. if (!_sendProgressBlock) {
  2099. return;
  2100. }
  2101. // We won't hold on to send progress block; it's ok to not send it if the upload finishes.
  2102. [self invokeOnCallbackQueueUnlessStopped:^{
  2103. GTMSessionFetcherSendProgressBlock progressBlock;
  2104. @synchronized(self) {
  2105. GTMSessionMonitorSynchronized(self);
  2106. progressBlock = _sendProgressBlock;
  2107. }
  2108. if (progressBlock) {
  2109. progressBlock(bytesSent, totalBytesSent, totalBytesExpectedToSend);
  2110. }
  2111. }];
  2112. } // @synchronized(self)
  2113. }
  2114. - (void)URLSession:(NSURLSession *)session
  2115. dataTask:(NSURLSessionDataTask *)dataTask
  2116. didReceiveData:(NSData *)data {
  2117. [self setSessionTask:dataTask];
  2118. NSUInteger bufferLength = data.length;
  2119. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ didReceiveData:%p (%llu bytes)",
  2120. [self class], self, session, dataTask, data,
  2121. (unsigned long long)bufferLength);
  2122. if (bufferLength == 0) {
  2123. // Observed on completing an out-of-process upload.
  2124. return;
  2125. }
  2126. @synchronized(self) {
  2127. GTMSessionMonitorSynchronized(self);
  2128. GTMSessionFetcherAccumulateDataBlock accumulateBlock = _accumulateDataBlock;
  2129. if (accumulateBlock) {
  2130. // Let the client accumulate the data.
  2131. _downloadedLength += bufferLength;
  2132. [self invokeOnCallbackQueueUnlessStopped:^{
  2133. accumulateBlock(data);
  2134. }];
  2135. } else if (!_userStoppedFetching) {
  2136. // Append to the mutable data buffer unless the fetch has been cancelled.
  2137. // Resumed upload tasks may not yet have a data buffer.
  2138. if (_downloadedData == nil) {
  2139. // Using NSClassFromString for iOS 6 compatibility.
  2140. GTMSESSION_ASSERT_DEBUG(
  2141. ![dataTask isKindOfClass:NSClassFromString(@"NSURLSessionDownloadTask")],
  2142. @"Resumed download tasks should not receive data bytes");
  2143. _downloadedData = [[NSMutableData alloc] init];
  2144. }
  2145. [_downloadedData appendData:data];
  2146. _downloadedLength = (int64_t)_downloadedData.length;
  2147. // We won't hold on to receivedProgressBlock here; it's ok to not send
  2148. // it if the transfer finishes.
  2149. if (_receivedProgressBlock) {
  2150. [self invokeOnCallbackQueueUnlessStopped:^{
  2151. GTMSessionFetcherReceivedProgressBlock progressBlock;
  2152. @synchronized(self) {
  2153. GTMSessionMonitorSynchronized(self);
  2154. progressBlock = _receivedProgressBlock;
  2155. }
  2156. if (progressBlock) {
  2157. progressBlock((int64_t)bufferLength, _downloadedLength);
  2158. }
  2159. }];
  2160. }
  2161. }
  2162. } // @synchronized(self)
  2163. }
  2164. - (void)URLSession:(NSURLSession *)session
  2165. dataTask:(NSURLSessionDataTask *)dataTask
  2166. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  2167. completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
  2168. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ dataTask:%@ willCacheResponse:%@ %@",
  2169. [self class], self, session, dataTask,
  2170. proposedResponse, proposedResponse.response);
  2171. GTMSessionFetcherWillCacheURLResponseBlock callback;
  2172. @synchronized(self) {
  2173. GTMSessionMonitorSynchronized(self);
  2174. callback = _willCacheURLResponseBlock;
  2175. if (callback) {
  2176. [self invokeOnCallbackQueueAfterUserStopped:YES
  2177. block:^{
  2178. callback(proposedResponse, completionHandler);
  2179. }];
  2180. }
  2181. } // @synchronized(self)
  2182. if (!callback) {
  2183. completionHandler(proposedResponse);
  2184. }
  2185. }
  2186. - (void)URLSession:(NSURLSession *)session
  2187. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  2188. didWriteData:(int64_t)bytesWritten
  2189. totalBytesWritten:(int64_t)totalBytesWritten
  2190. totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
  2191. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didWriteData:%lld"
  2192. @" bytesWritten:%lld totalBytesExpectedToWrite:%lld",
  2193. [self class], self, session, downloadTask, bytesWritten,
  2194. totalBytesWritten, totalBytesExpectedToWrite);
  2195. [self setSessionTask:downloadTask];
  2196. @synchronized(self) {
  2197. GTMSessionMonitorSynchronized(self);
  2198. if ((totalBytesExpectedToWrite != NSURLSessionTransferSizeUnknown) &&
  2199. (totalBytesExpectedToWrite < totalBytesWritten)) {
  2200. // Have observed cases were bytesWritten == totalBytesExpectedToWrite,
  2201. // but totalBytesWritten > totalBytesExpectedToWrite, so setting to unkown in these cases.
  2202. totalBytesExpectedToWrite = NSURLSessionTransferSizeUnknown;
  2203. }
  2204. // We won't hold on to download progress block during the enqueue;
  2205. // it's ok to not send it if the upload finishes.
  2206. [self invokeOnCallbackQueueUnlessStopped:^{
  2207. GTMSessionFetcherDownloadProgressBlock progressBlock;
  2208. @synchronized(self) {
  2209. GTMSessionMonitorSynchronized(self);
  2210. progressBlock = _downloadProgressBlock;
  2211. }
  2212. if (progressBlock) {
  2213. progressBlock(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
  2214. }
  2215. }];
  2216. } // @synchronized(self)
  2217. }
  2218. - (void)URLSession:(NSURLSession *)session
  2219. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  2220. didResumeAtOffset:(int64_t)fileOffset
  2221. expectedTotalBytes:(int64_t)expectedTotalBytes {
  2222. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didResumeAtOffset:%lld"
  2223. @" expectedTotalBytes:%lld",
  2224. [self class], self, session, downloadTask, fileOffset,
  2225. expectedTotalBytes);
  2226. [self setSessionTask:downloadTask];
  2227. }
  2228. - (void)URLSession:(NSURLSession *)session
  2229. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  2230. didFinishDownloadingToURL:(NSURL *)downloadLocationURL {
  2231. // Download may have relaunched app, so update _sessionTask.
  2232. [self setSessionTask:downloadTask];
  2233. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ downloadTask:%@ didFinishDownloadingToURL:%@",
  2234. [self class], self, session, downloadTask, downloadLocationURL);
  2235. NSNumber *fileSizeNum;
  2236. [downloadLocationURL getResourceValue:&fileSizeNum
  2237. forKey:NSURLFileSizeKey
  2238. error:NULL];
  2239. @synchronized(self) {
  2240. GTMSessionMonitorSynchronized(self);
  2241. NSURL *destinationURL = _destinationFileURL;
  2242. _downloadedLength = fileSizeNum.longLongValue;
  2243. // Overwrite any previous file at the destination URL.
  2244. NSFileManager *fileMgr = [NSFileManager defaultManager];
  2245. NSError *removeError;
  2246. if (![fileMgr removeItemAtURL:destinationURL error:&removeError]
  2247. && removeError.code != NSFileNoSuchFileError) {
  2248. GTMSESSION_LOG_DEBUG(@"Could not remove previous file at %@ due to %@",
  2249. downloadLocationURL.path, removeError);
  2250. }
  2251. NSInteger statusCode = [self statusCodeUnsynchronized];
  2252. if (statusCode < 200 || statusCode > 399) {
  2253. // In OS X 10.11, the response body is written to a file even on a server
  2254. // status error. For convenience of the fetcher client, we'll skip saving the
  2255. // downloaded body to the destination URL so that clients do not need to know
  2256. // to delete the file following fetch errors. A downside of this is that
  2257. // the server may have included error details in the response body, and
  2258. // abandoning the downloaded file here means that the details from the
  2259. // body are not available to the fetcher client.
  2260. GTMSESSION_LOG_DEBUG(@"Abandoning download due to status %zd, file %@",
  2261. statusCode, downloadLocationURL.path);
  2262. } else {
  2263. NSError *moveError;
  2264. NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent];
  2265. BOOL didMoveDownload = NO;
  2266. if ([fileMgr createDirectoryAtURL:destinationFolderURL
  2267. withIntermediateDirectories:YES
  2268. attributes:nil
  2269. error:&moveError]) {
  2270. didMoveDownload = [fileMgr moveItemAtURL:downloadLocationURL
  2271. toURL:destinationURL
  2272. error:&moveError];
  2273. }
  2274. if (!didMoveDownload) {
  2275. _downloadFinishedError = moveError;
  2276. }
  2277. GTM_LOG_BACKGROUND_SESSION(@"%@ %p Moved download from \"%@\" to \"%@\" %@",
  2278. [self class], self,
  2279. downloadLocationURL.path, destinationURL.path,
  2280. error ? error : @"");
  2281. }
  2282. } // @synchronized(self)
  2283. }
  2284. /* Sent as the last message related to a specific task. Error may be
  2285. * nil, which implies that no error occurred and this task is complete.
  2286. */
  2287. - (void)URLSession:(NSURLSession *)session
  2288. task:(NSURLSessionTask *)task
  2289. didCompleteWithError:(NSError *)error {
  2290. [self setSessionTask:task];
  2291. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ task:%@ didCompleteWithError:%@",
  2292. [self class], self, session, task, error);
  2293. NSInteger status = self.statusCode;
  2294. BOOL forceAssumeRetry = NO;
  2295. BOOL succeeded = NO;
  2296. @synchronized(self) {
  2297. GTMSessionMonitorSynchronized(self);
  2298. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  2299. // The task is never resumed when a testBlock is used. When the session is destroyed,
  2300. // we should ignore the callback, since the testBlock support code itself invokes
  2301. // shouldRetryNowForStatus: and finishWithError:shouldRetry:
  2302. if (_isUsingTestBlock) return;
  2303. #endif
  2304. if (error == nil) {
  2305. error = _downloadFinishedError;
  2306. }
  2307. succeeded = (error == nil && status >= 0 && status < 300);
  2308. if (succeeded) {
  2309. // Succeeded.
  2310. _bodyLength = task.countOfBytesSent;
  2311. }
  2312. } // @synchronized(self)
  2313. if (succeeded) {
  2314. [self finishWithError:nil shouldRetry:NO];
  2315. return;
  2316. }
  2317. // For background redirects, no delegate method is called, so we cannot restore a stripped
  2318. // Authorization header, so if a 403 ("Forbidden") was generated due to a missing OAuth 2 header,
  2319. // set the current request's URL to the redirected URL, so we in effect restore the Authorization
  2320. // header.
  2321. if ((status == 403) && self.usingBackgroundSession) {
  2322. NSURL *redirectURL = self.response.URL;
  2323. NSURLRequest *request = self.request;
  2324. if (![request.URL isEqual:redirectURL]) {
  2325. NSString *authorizationHeader = [request.allHTTPHeaderFields objectForKey:@"Authorization"];
  2326. if (authorizationHeader != nil) {
  2327. NSMutableURLRequest *mutableRequest = [request mutableCopy];
  2328. mutableRequest.URL = redirectURL;
  2329. [self updateMutableRequest:mutableRequest];
  2330. // Avoid assuming the session is still valid.
  2331. self.session = nil;
  2332. forceAssumeRetry = YES;
  2333. }
  2334. }
  2335. }
  2336. // If invalidating the session was deferred in stopFetchReleasingCallbacks: then do it now.
  2337. NSURLSession *oldSession = self.sessionNeedingInvalidation;
  2338. if (oldSession) {
  2339. [self setSessionNeedingInvalidation:NULL];
  2340. [oldSession finishTasksAndInvalidate];
  2341. }
  2342. // Failed.
  2343. [self shouldRetryNowForStatus:status
  2344. error:error
  2345. forceAssumeRetry:forceAssumeRetry
  2346. response:^(BOOL shouldRetry) {
  2347. [self finishWithError:error shouldRetry:shouldRetry];
  2348. }];
  2349. }
  2350. #if TARGET_OS_IPHONE
  2351. - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
  2352. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSessionDidFinishEventsForBackgroundURLSession:%@",
  2353. [self class], self, session);
  2354. [self removePersistedBackgroundSessionFromDefaults];
  2355. GTMSessionFetcherSystemCompletionHandler handler;
  2356. @synchronized(self) {
  2357. GTMSessionMonitorSynchronized(self);
  2358. handler = self.systemCompletionHandler;
  2359. self.systemCompletionHandler = nil;
  2360. } // @synchronized(self)
  2361. if (handler) {
  2362. GTM_LOG_BACKGROUND_SESSION(@"%@ %p Calling system completionHandler", [self class], self);
  2363. handler();
  2364. @synchronized(self) {
  2365. GTMSessionMonitorSynchronized(self);
  2366. NSURLSession *oldSession = _session;
  2367. _session = nil;
  2368. if (_shouldInvalidateSession) {
  2369. [oldSession finishTasksAndInvalidate];
  2370. }
  2371. } // @synchronized(self)
  2372. }
  2373. }
  2374. #endif
  2375. - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(GTM_NULLABLE NSError *)error {
  2376. // This may happen repeatedly for retries. On authentication callbacks, the retry
  2377. // may begin before the prior session sends the didBecomeInvalid delegate message.
  2378. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@",
  2379. [self class], self, session, error);
  2380. if (session == (NSURLSession *)self.session) {
  2381. GTM_LOG_SESSION_DELEGATE(@" Unexpected retained invalid session: %@", session);
  2382. self.session = nil;
  2383. }
  2384. }
  2385. - (void)finishWithError:(GTM_NULLABLE NSError *)error shouldRetry:(BOOL)shouldRetry {
  2386. [self removePersistedBackgroundSessionFromDefaults];
  2387. BOOL shouldStopFetching = YES;
  2388. NSData *downloadedData = nil;
  2389. #if !STRIP_GTM_FETCH_LOGGING
  2390. BOOL shouldDeferLogging = NO;
  2391. #endif
  2392. BOOL shouldBeginRetryTimer = NO;
  2393. NSInteger status = [self statusCode];
  2394. NSURL *destinationURL = self.destinationFileURL;
  2395. BOOL fetchSucceeded = (error == nil && status >= 0 && status < 300);
  2396. #if !STRIP_GTM_FETCH_LOGGING
  2397. if (!fetchSucceeded) {
  2398. if (!shouldDeferLogging && !self.hasLoggedError) {
  2399. [self logNowWithError:error];
  2400. self.hasLoggedError = YES;
  2401. }
  2402. }
  2403. #endif // !STRIP_GTM_FETCH_LOGGING
  2404. @synchronized(self) {
  2405. GTMSessionMonitorSynchronized(self);
  2406. #if !STRIP_GTM_FETCH_LOGGING
  2407. shouldDeferLogging = _deferResponseBodyLogging;
  2408. #endif
  2409. if (fetchSucceeded) {
  2410. // Success
  2411. if ((_downloadedData.length > 0) && (destinationURL != nil)) {
  2412. // Overwrite any previous file at the destination URL.
  2413. NSFileManager *fileMgr = [NSFileManager defaultManager];
  2414. [fileMgr removeItemAtURL:destinationURL
  2415. error:NULL];
  2416. NSURL *destinationFolderURL = [destinationURL URLByDeletingLastPathComponent];
  2417. BOOL didMoveDownload = NO;
  2418. if ([fileMgr createDirectoryAtURL:destinationFolderURL
  2419. withIntermediateDirectories:YES
  2420. attributes:nil
  2421. error:&error]) {
  2422. didMoveDownload = [_downloadedData writeToURL:destinationURL
  2423. options:NSDataWritingAtomic
  2424. error:&error];
  2425. }
  2426. if (didMoveDownload) {
  2427. _downloadedData = nil;
  2428. } else {
  2429. _downloadFinishedError = error;
  2430. }
  2431. }
  2432. downloadedData = _downloadedData;
  2433. } else {
  2434. // Unsuccessful with error or status over 300. Retry or notify the delegate of failure
  2435. if (shouldRetry) {
  2436. // Retrying.
  2437. shouldBeginRetryTimer = YES;
  2438. shouldStopFetching = NO;
  2439. } else {
  2440. if (error == nil) {
  2441. // Create an error.
  2442. NSDictionary *userInfo = nil;
  2443. if (_downloadedData.length > 0) {
  2444. userInfo = @{ kGTMSessionFetcherStatusDataKey : _downloadedData };
  2445. }
  2446. error = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain
  2447. code:status
  2448. userInfo:userInfo];
  2449. } else {
  2450. // If the error had resume data, and the client supplied a resume block, pass the
  2451. // data to the client.
  2452. void (^resumeBlock)(NSData *) = _resumeDataBlock;
  2453. _resumeDataBlock = nil;
  2454. if (resumeBlock) {
  2455. NSData *resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
  2456. if (resumeData) {
  2457. [self invokeOnCallbackQueueAfterUserStopped:YES block:^{
  2458. resumeBlock(resumeData);
  2459. }];
  2460. }
  2461. }
  2462. }
  2463. if (_downloadedData.length > 0) {
  2464. downloadedData = _downloadedData;
  2465. }
  2466. // If the error occurred after retries, report the number and duration of the
  2467. // retries. This provides a clue to a developer looking at the error description
  2468. // that the fetcher did retry before failing with this error.
  2469. if (_retryCount > 0) {
  2470. NSMutableDictionary *userInfoWithRetries =
  2471. [NSMutableDictionary dictionaryWithDictionary:(NSDictionary *)error.userInfo];
  2472. NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow];
  2473. [userInfoWithRetries setObject:@(timeSinceInitialRequest)
  2474. forKey:kGTMSessionFetcherElapsedIntervalWithRetriesKey];
  2475. [userInfoWithRetries setObject:@(_retryCount)
  2476. forKey:kGTMSessionFetcherNumberOfRetriesDoneKey];
  2477. error = [NSError errorWithDomain:(NSString *)error.domain
  2478. code:error.code
  2479. userInfo:userInfoWithRetries];
  2480. }
  2481. }
  2482. }
  2483. } // @synchronized(self)
  2484. if (shouldBeginRetryTimer) {
  2485. [self beginRetryTimer];
  2486. }
  2487. // We want to send the stop notification before calling the delegate's
  2488. // callback selector, since the callback selector may release all of
  2489. // the fetcher properties that the client is using to track the fetches.
  2490. //
  2491. // We'll also stop now so that, to any observers watching the notifications,
  2492. // it doesn't look like our wait for a retry (which may be long,
  2493. // 30 seconds or more) is part of the network activity.
  2494. [self sendStopNotificationIfNeeded];
  2495. if (shouldStopFetching) {
  2496. [self invokeFetchCallbacksOnCallbackQueueWithData:downloadedData
  2497. error:error];
  2498. // The upload subclass doesn't want to release callbacks until upload chunks have completed.
  2499. BOOL shouldRelease = [self shouldReleaseCallbacksUponCompletion];
  2500. [self stopFetchReleasingCallbacks:shouldRelease];
  2501. }
  2502. #if !STRIP_GTM_FETCH_LOGGING
  2503. // _hasLoggedError is only set by this method
  2504. if (!shouldDeferLogging && !_hasLoggedError) {
  2505. [self logNowWithError:error];
  2506. }
  2507. #endif
  2508. }
  2509. - (BOOL)shouldReleaseCallbacksUponCompletion {
  2510. // A subclass can override this to keep callbacks around after the
  2511. // connection has finished successfully
  2512. return YES;
  2513. }
  2514. - (void)logNowWithError:(GTM_NULLABLE NSError *)error {
  2515. GTMSessionCheckNotSynchronized(self);
  2516. // If the logging category is available, then log the current request,
  2517. // response, data, and error
  2518. if ([self respondsToSelector:@selector(logFetchWithError:)]) {
  2519. [self performSelector:@selector(logFetchWithError:) withObject:error];
  2520. }
  2521. }
  2522. #pragma mark Retries
  2523. - (BOOL)isRetryError:(NSError *)error {
  2524. struct RetryRecord {
  2525. __unsafe_unretained NSString *const domain;
  2526. NSInteger code;
  2527. };
  2528. struct RetryRecord retries[] = {
  2529. { kGTMSessionFetcherStatusDomain, 408 }, // request timeout
  2530. { kGTMSessionFetcherStatusDomain, 502 }, // failure gatewaying to another server
  2531. { kGTMSessionFetcherStatusDomain, 503 }, // service unavailable
  2532. { kGTMSessionFetcherStatusDomain, 504 }, // request timeout
  2533. { NSURLErrorDomain, NSURLErrorTimedOut },
  2534. { NSURLErrorDomain, NSURLErrorNetworkConnectionLost },
  2535. { nil, 0 }
  2536. };
  2537. // NSError's isEqual always returns false for equal but distinct instances
  2538. // of NSError, so we have to compare the domain and code values explicitly
  2539. NSString *domain = error.domain;
  2540. NSInteger code = error.code;
  2541. for (int idx = 0; retries[idx].domain != nil; idx++) {
  2542. if (code == retries[idx].code && [domain isEqual:retries[idx].domain]) {
  2543. return YES;
  2544. }
  2545. }
  2546. return NO;
  2547. }
  2548. // shouldRetryNowForStatus:error: responds with YES if the user has enabled retries
  2549. // and the status or error is one that is suitable for retrying. "Suitable"
  2550. // means either the isRetryError:'s list contains the status or error, or the
  2551. // user's retry block is present and returns YES when called, or the
  2552. // authorizer may be able to fix.
  2553. - (void)shouldRetryNowForStatus:(NSInteger)status
  2554. error:(NSError *)error
  2555. forceAssumeRetry:(BOOL)forceAssumeRetry
  2556. response:(GTMSessionFetcherRetryResponse)response {
  2557. // Determine if a refreshed authorizer may avoid an authorization error
  2558. BOOL willRetry = NO;
  2559. // We assume _authorizer is immutable after beginFetch, and _hasAttemptedAuthRefresh is modified
  2560. // only in this method, and this method is invoked on the serial delegate queue.
  2561. //
  2562. // We want to avoid calling the authorizer from inside a sync block.
  2563. BOOL isFirstAuthError = (_authorizer != nil
  2564. && !_hasAttemptedAuthRefresh
  2565. && status == GTMSessionFetcherStatusUnauthorized); // 401
  2566. BOOL hasPrimed = NO;
  2567. if (isFirstAuthError) {
  2568. if ([_authorizer respondsToSelector:@selector(primeForRefresh)]) {
  2569. hasPrimed = [_authorizer primeForRefresh];
  2570. }
  2571. }
  2572. BOOL shouldRetryForAuthRefresh = NO;
  2573. if (hasPrimed) {
  2574. shouldRetryForAuthRefresh = YES;
  2575. _hasAttemptedAuthRefresh = YES;
  2576. [self updateRequestValue:nil forHTTPHeaderField:@"Authorization"];
  2577. }
  2578. @synchronized(self) {
  2579. GTMSessionMonitorSynchronized(self);
  2580. BOOL shouldDoRetry = [self isRetryEnabledUnsynchronized];
  2581. if (shouldDoRetry && ![self hasRetryAfterInterval]) {
  2582. // Determine if we're doing exponential backoff retries
  2583. shouldDoRetry = [self nextRetryIntervalUnsynchronized] < _maxRetryInterval;
  2584. if (shouldDoRetry) {
  2585. // If an explicit max retry interval was set, we expect repeated backoffs to take
  2586. // up to roughly twice that for repeated fast failures. If the initial attempt is
  2587. // already more than 3 times the max retry interval, then failures have taken a long time
  2588. // (such as from network timeouts) so don't retry again to avoid the app becoming
  2589. // unexpectedly unresponsive.
  2590. if (_maxRetryInterval > 0) {
  2591. NSTimeInterval maxAllowedIntervalBeforeRetry = _maxRetryInterval * 3;
  2592. NSTimeInterval timeSinceInitialRequest = -[_initialRequestDate timeIntervalSinceNow];
  2593. if (timeSinceInitialRequest > maxAllowedIntervalBeforeRetry) {
  2594. shouldDoRetry = NO;
  2595. }
  2596. }
  2597. }
  2598. }
  2599. BOOL canRetry = shouldRetryForAuthRefresh || forceAssumeRetry || shouldDoRetry;
  2600. if (canRetry) {
  2601. NSDictionary *userInfo = nil;
  2602. if (_downloadedData.length > 0) {
  2603. userInfo = @{ kGTMSessionFetcherStatusDataKey : _downloadedData };
  2604. }
  2605. NSError *statusError = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain
  2606. code:status
  2607. userInfo:userInfo];
  2608. if (error == nil) {
  2609. error = statusError;
  2610. }
  2611. willRetry = shouldRetryForAuthRefresh ||
  2612. forceAssumeRetry ||
  2613. [self isRetryError:error] ||
  2614. ((error != statusError) && [self isRetryError:statusError]);
  2615. // If the user has installed a retry callback, consult that.
  2616. GTMSessionFetcherRetryBlock retryBlock = _retryBlock;
  2617. if (retryBlock) {
  2618. [self invokeOnCallbackQueueUnlessStopped:^{
  2619. retryBlock(willRetry, error, response);
  2620. }];
  2621. return;
  2622. }
  2623. }
  2624. } // @synchronized(self)
  2625. response(willRetry);
  2626. }
  2627. - (BOOL)hasRetryAfterInterval {
  2628. GTMSessionCheckSynchronized(self);
  2629. NSDictionary *responseHeaders = [self responseHeadersUnsynchronized];
  2630. NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"];
  2631. return (retryAfterValue != nil);
  2632. }
  2633. - (NSTimeInterval)retryAfterInterval {
  2634. GTMSessionCheckSynchronized(self);
  2635. NSDictionary *responseHeaders = [self responseHeadersUnsynchronized];
  2636. NSString *retryAfterValue = [responseHeaders valueForKey:@"Retry-After"];
  2637. if (retryAfterValue == nil) {
  2638. return 0;
  2639. }
  2640. // Retry-After formatted as HTTP-date | delta-seconds
  2641. // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
  2642. NSDateFormatter *rfc1123DateFormatter = [[NSDateFormatter alloc] init];
  2643. rfc1123DateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
  2644. rfc1123DateFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];
  2645. rfc1123DateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss z";
  2646. NSDate *retryAfterDate = [rfc1123DateFormatter dateFromString:retryAfterValue];
  2647. NSTimeInterval retryAfterInterval = (retryAfterDate != nil) ?
  2648. retryAfterDate.timeIntervalSinceNow : retryAfterValue.intValue;
  2649. retryAfterInterval = MAX(0, retryAfterInterval);
  2650. return retryAfterInterval;
  2651. }
  2652. - (void)beginRetryTimer {
  2653. if (![NSThread isMainThread]) {
  2654. // Defer creating and starting the timer until we're on the main thread to ensure it has
  2655. // a run loop.
  2656. dispatch_group_async(_callbackGroup, dispatch_get_main_queue(), ^{
  2657. [self beginRetryTimer];
  2658. });
  2659. return;
  2660. }
  2661. [self destroyRetryTimer];
  2662. @synchronized(self) {
  2663. GTMSessionMonitorSynchronized(self);
  2664. NSTimeInterval nextInterval = [self nextRetryIntervalUnsynchronized];
  2665. NSTimeInterval maxInterval = _maxRetryInterval;
  2666. NSTimeInterval newInterval = MIN(nextInterval, (maxInterval > 0 ? maxInterval : DBL_MAX));
  2667. NSTimeInterval newIntervalTolerance = (newInterval / 10) > 1.0 ?: 1.0;
  2668. _lastRetryInterval = newInterval;
  2669. _retryTimer = [NSTimer timerWithTimeInterval:newInterval
  2670. target:self
  2671. selector:@selector(retryTimerFired:)
  2672. userInfo:nil
  2673. repeats:NO];
  2674. _retryTimer.tolerance = newIntervalTolerance;
  2675. [[NSRunLoop mainRunLoop] addTimer:_retryTimer
  2676. forMode:NSDefaultRunLoopMode];
  2677. } // @synchronized(self)
  2678. [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStartedNotification
  2679. userInfo:nil
  2680. requireAsync:NO];
  2681. }
  2682. - (void)retryTimerFired:(NSTimer *)timer {
  2683. [self destroyRetryTimer];
  2684. @synchronized(self) {
  2685. GTMSessionMonitorSynchronized(self);
  2686. _retryCount++;
  2687. } // @synchronized(self)
  2688. NSOperationQueue *queue = self.sessionDelegateQueue;
  2689. [queue addOperationWithBlock:^{
  2690. [self retryFetch];
  2691. }];
  2692. }
  2693. - (void)destroyRetryTimer {
  2694. BOOL shouldNotify = NO;
  2695. @synchronized(self) {
  2696. GTMSessionMonitorSynchronized(self);
  2697. if (_retryTimer) {
  2698. [_retryTimer invalidate];
  2699. _retryTimer = nil;
  2700. shouldNotify = YES;
  2701. }
  2702. }
  2703. if (shouldNotify) {
  2704. [self postNotificationOnMainThreadWithName:kGTMSessionFetcherRetryDelayStoppedNotification
  2705. userInfo:nil
  2706. requireAsync:NO];
  2707. }
  2708. }
  2709. - (NSUInteger)retryCount {
  2710. @synchronized(self) {
  2711. GTMSessionMonitorSynchronized(self);
  2712. return _retryCount;
  2713. } // @synchronized(self)
  2714. }
  2715. - (NSTimeInterval)nextRetryInterval {
  2716. @synchronized(self) {
  2717. GTMSessionMonitorSynchronized(self);
  2718. NSTimeInterval interval = [self nextRetryIntervalUnsynchronized];
  2719. return interval;
  2720. } // @synchronized(self)
  2721. }
  2722. - (NSTimeInterval)nextRetryIntervalUnsynchronized {
  2723. GTMSessionCheckSynchronized(self);
  2724. NSInteger statusCode = [self statusCodeUnsynchronized];
  2725. if ((statusCode == 503) && [self hasRetryAfterInterval]) {
  2726. NSTimeInterval secs = [self retryAfterInterval];
  2727. return secs;
  2728. }
  2729. // The next wait interval is the factor (2.0) times the last interval,
  2730. // but never less than the minimum interval.
  2731. NSTimeInterval secs = _lastRetryInterval * _retryFactor;
  2732. if (_maxRetryInterval > 0) {
  2733. secs = MIN(secs, _maxRetryInterval);
  2734. }
  2735. secs = MAX(secs, _minRetryInterval);
  2736. return secs;
  2737. }
  2738. - (NSTimer *)retryTimer {
  2739. @synchronized(self) {
  2740. GTMSessionMonitorSynchronized(self);
  2741. return _retryTimer;
  2742. } // @synchronized(self)
  2743. }
  2744. - (BOOL)isRetryEnabled {
  2745. @synchronized(self) {
  2746. GTMSessionMonitorSynchronized(self);
  2747. return _isRetryEnabled;
  2748. } // @synchronized(self)
  2749. }
  2750. - (BOOL)isRetryEnabledUnsynchronized {
  2751. GTMSessionCheckSynchronized(self);
  2752. return _isRetryEnabled;
  2753. }
  2754. - (void)setRetryEnabled:(BOOL)flag {
  2755. @synchronized(self) {
  2756. GTMSessionMonitorSynchronized(self);
  2757. if (flag && !_isRetryEnabled) {
  2758. // We defer initializing these until the user calls setRetryEnabled
  2759. // to avoid using the random number generator if it's not needed.
  2760. // However, this means min and max intervals for this fetcher are reset
  2761. // as a side effect of calling setRetryEnabled.
  2762. //
  2763. // Make an initial retry interval random between 1.0 and 2.0 seconds
  2764. _minRetryInterval = InitialMinRetryInterval();
  2765. _maxRetryInterval = kUnsetMaxRetryInterval;
  2766. _retryFactor = 2.0;
  2767. _lastRetryInterval = 0.0;
  2768. }
  2769. _isRetryEnabled = flag;
  2770. } // @synchronized(self)
  2771. };
  2772. - (NSTimeInterval)maxRetryInterval {
  2773. @synchronized(self) {
  2774. GTMSessionMonitorSynchronized(self);
  2775. return _maxRetryInterval;
  2776. } // @synchronized(self)
  2777. }
  2778. - (void)setMaxRetryInterval:(NSTimeInterval)secs {
  2779. @synchronized(self) {
  2780. GTMSessionMonitorSynchronized(self);
  2781. if (secs > 0) {
  2782. _maxRetryInterval = secs;
  2783. } else {
  2784. _maxRetryInterval = kUnsetMaxRetryInterval;
  2785. }
  2786. } // @synchronized(self)
  2787. }
  2788. - (double)minRetryInterval {
  2789. @synchronized(self) {
  2790. GTMSessionMonitorSynchronized(self);
  2791. return _minRetryInterval;
  2792. } // @synchronized(self)
  2793. }
  2794. - (void)setMinRetryInterval:(NSTimeInterval)secs {
  2795. @synchronized(self) {
  2796. GTMSessionMonitorSynchronized(self);
  2797. if (secs > 0) {
  2798. _minRetryInterval = secs;
  2799. } else {
  2800. // Set min interval to a random value between 1.0 and 2.0 seconds
  2801. // so that if multiple clients start retrying at the same time, they'll
  2802. // repeat at different times and avoid overloading the server
  2803. _minRetryInterval = InitialMinRetryInterval();
  2804. }
  2805. } // @synchronized(self)
  2806. }
  2807. #pragma mark iOS System Completion Handlers
  2808. #if TARGET_OS_IPHONE
  2809. static NSMutableDictionary *gSystemCompletionHandlers = nil;
  2810. - (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler {
  2811. return [[self class] systemCompletionHandlerForSessionIdentifier:_sessionIdentifier];
  2812. }
  2813. - (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler {
  2814. [[self class] setSystemCompletionHandler:systemCompletionHandler
  2815. forSessionIdentifier:_sessionIdentifier];
  2816. }
  2817. + (void)setSystemCompletionHandler:(GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandler
  2818. forSessionIdentifier:(NSString *)sessionIdentifier {
  2819. if (!sessionIdentifier) {
  2820. NSLog(@"%s with nil identifier", __PRETTY_FUNCTION__);
  2821. return;
  2822. }
  2823. @synchronized([GTMSessionFetcher class]) {
  2824. if (gSystemCompletionHandlers == nil && systemCompletionHandler != nil) {
  2825. gSystemCompletionHandlers = [[NSMutableDictionary alloc] init];
  2826. }
  2827. // Use setValue: to remove the object if completionHandler is nil.
  2828. [gSystemCompletionHandlers setValue:systemCompletionHandler
  2829. forKey:sessionIdentifier];
  2830. }
  2831. }
  2832. + (GTM_NULLABLE GTMSessionFetcherSystemCompletionHandler)systemCompletionHandlerForSessionIdentifier:(NSString *)sessionIdentifier {
  2833. if (!sessionIdentifier) {
  2834. return nil;
  2835. }
  2836. @synchronized([GTMSessionFetcher class]) {
  2837. return [gSystemCompletionHandlers objectForKey:sessionIdentifier];
  2838. }
  2839. }
  2840. #endif // TARGET_OS_IPHONE
  2841. #pragma mark Getters and Setters
  2842. @synthesize downloadResumeData = _downloadResumeData,
  2843. configuration = _configuration,
  2844. configurationBlock = _configurationBlock,
  2845. sessionTask = _sessionTask,
  2846. wasCreatedFromBackgroundSession = _wasCreatedFromBackgroundSession,
  2847. sessionUserInfo = _sessionUserInfo,
  2848. taskDescription = _taskDescription,
  2849. taskPriority = _taskPriority,
  2850. usingBackgroundSession = _usingBackgroundSession,
  2851. canShareSession = _canShareSession,
  2852. completionHandler = _completionHandler,
  2853. credential = _credential,
  2854. proxyCredential = _proxyCredential,
  2855. bodyData = _bodyData,
  2856. bodyLength = _bodyLength,
  2857. service = _service,
  2858. serviceHost = _serviceHost,
  2859. accumulateDataBlock = _accumulateDataBlock,
  2860. receivedProgressBlock = _receivedProgressBlock,
  2861. downloadProgressBlock = _downloadProgressBlock,
  2862. resumeDataBlock = _resumeDataBlock,
  2863. didReceiveResponseBlock = _didReceiveResponseBlock,
  2864. challengeBlock = _challengeBlock,
  2865. willRedirectBlock = _willRedirectBlock,
  2866. sendProgressBlock = _sendProgressBlock,
  2867. willCacheURLResponseBlock = _willCacheURLResponseBlock,
  2868. retryBlock = _retryBlock,
  2869. retryFactor = _retryFactor,
  2870. allowedInsecureSchemes = _allowedInsecureSchemes,
  2871. allowLocalhostRequest = _allowLocalhostRequest,
  2872. allowInvalidServerCertificates = _allowInvalidServerCertificates,
  2873. cookieStorage = _cookieStorage,
  2874. callbackQueue = _callbackQueue,
  2875. initialBeginFetchDate = _initialBeginFetchDate,
  2876. testBlock = _testBlock,
  2877. comment = _comment,
  2878. log = _log;
  2879. #if !STRIP_GTM_FETCH_LOGGING
  2880. @synthesize redirectedFromURL = _redirectedFromURL,
  2881. logRequestBody = _logRequestBody,
  2882. logResponseBody = _logResponseBody,
  2883. hasLoggedError = _hasLoggedError;
  2884. #endif
  2885. #if GTM_BACKGROUND_TASK_FETCHING
  2886. @synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier,
  2887. skipBackgroundTask = _skipBackgroundTask;
  2888. #endif
  2889. - (GTM_NULLABLE NSURLRequest *)request {
  2890. @synchronized(self) {
  2891. GTMSessionMonitorSynchronized(self);
  2892. return [_request copy];
  2893. } // @synchronized(self)
  2894. }
  2895. - (void)setRequest:(GTM_NULLABLE NSURLRequest *)request {
  2896. @synchronized(self) {
  2897. GTMSessionMonitorSynchronized(self);
  2898. if (![self isFetchingUnsynchronized]) {
  2899. _request = [request mutableCopy];
  2900. } else {
  2901. GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked");
  2902. }
  2903. } // @synchronized(self)
  2904. }
  2905. - (GTM_NULLABLE NSMutableURLRequest *)mutableRequestForTesting {
  2906. // Allow tests only to modify the request, useful during retries.
  2907. return _request;
  2908. }
  2909. - (GTM_NULLABLE NSMutableURLRequest *)mutableRequest {
  2910. @synchronized(self) {
  2911. GTMSessionMonitorSynchronized(self);
  2912. GTMSESSION_LOG_DEBUG(@"[GTMSessionFetcher mutableRequest] is deprecated; use -request or"
  2913. @" -setRequestVaue:forHTTPHeaderField:");
  2914. return _request;
  2915. } // @synchronized(self)
  2916. }
  2917. - (void)setMutableRequest:(GTM_NULLABLE NSMutableURLRequest *)request {
  2918. GTMSESSION_LOG_DEBUG(@"[GTMSessionFetcher setMutableRequest:] is deprecated; use -request or"
  2919. @" -setRequestVaue:forHTTPHeaderField:");
  2920. GTMSESSION_ASSERT_DEBUG(![self isFetching],
  2921. @"mutableRequest should not change after beginFetch has been invoked");
  2922. [self updateMutableRequest:request];
  2923. }
  2924. // Internal method for updating the request property such as on redirects.
  2925. - (void)updateMutableRequest:(GTM_NULLABLE NSMutableURLRequest *)request {
  2926. @synchronized(self) {
  2927. GTMSessionMonitorSynchronized(self);
  2928. _request = request;
  2929. } // @synchronized(self)
  2930. }
  2931. // Set a header field value on the request. Header field value changes will not
  2932. // affect a fetch after the fetch has begun.
  2933. - (void)setRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field {
  2934. if (![self isFetching]) {
  2935. [self updateRequestValue:value forHTTPHeaderField:field];
  2936. } else {
  2937. GTMSESSION_ASSERT_DEBUG(0, @"request may not be set after beginFetch has been invoked");
  2938. }
  2939. }
  2940. // Internal method for updating request headers.
  2941. - (void)updateRequestValue:(GTM_NULLABLE NSString *)value forHTTPHeaderField:(NSString *)field {
  2942. @synchronized(self) {
  2943. GTMSessionMonitorSynchronized(self);
  2944. [_request setValue:value forHTTPHeaderField:field];
  2945. } // @synchronized(self)
  2946. }
  2947. - (void)setResponse:(GTM_NULLABLE NSURLResponse *)response {
  2948. @synchronized(self) {
  2949. GTMSessionMonitorSynchronized(self);
  2950. _response = response;
  2951. } // @synchronized(self)
  2952. }
  2953. - (int64_t)bodyLength {
  2954. @synchronized(self) {
  2955. GTMSessionMonitorSynchronized(self);
  2956. if (_bodyLength == NSURLSessionTransferSizeUnknown) {
  2957. if (_bodyData) {
  2958. _bodyLength = (int64_t)_bodyData.length;
  2959. } else if (_bodyFileURL) {
  2960. NSNumber *fileSizeNum = nil;
  2961. NSError *fileSizeError = nil;
  2962. if ([_bodyFileURL getResourceValue:&fileSizeNum
  2963. forKey:NSURLFileSizeKey
  2964. error:&fileSizeError]) {
  2965. _bodyLength = [fileSizeNum longLongValue];
  2966. }
  2967. }
  2968. }
  2969. return _bodyLength;
  2970. } // @synchronized(self)
  2971. }
  2972. - (BOOL)useUploadTask {
  2973. @synchronized(self) {
  2974. GTMSessionMonitorSynchronized(self);
  2975. return _useUploadTask;
  2976. } // @synchronized(self)
  2977. }
  2978. - (void)setUseUploadTask:(BOOL)flag {
  2979. @synchronized(self) {
  2980. GTMSessionMonitorSynchronized(self);
  2981. if (flag != _useUploadTask) {
  2982. GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized],
  2983. @"useUploadTask should not change after beginFetch has been invoked");
  2984. _useUploadTask = flag;
  2985. }
  2986. } // @synchronized(self)
  2987. }
  2988. - (GTM_NULLABLE NSURL *)bodyFileURL {
  2989. @synchronized(self) {
  2990. GTMSessionMonitorSynchronized(self);
  2991. return _bodyFileURL;
  2992. } // @synchronized(self)
  2993. }
  2994. - (void)setBodyFileURL:(GTM_NULLABLE NSURL *)fileURL {
  2995. @synchronized(self) {
  2996. GTMSessionMonitorSynchronized(self);
  2997. // The comparison here is a trivial optimization and forgiveness for any client that
  2998. // repeatedly sets the property, so it just uses pointer comparison rather than isEqual:.
  2999. if (fileURL != _bodyFileURL) {
  3000. GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized],
  3001. @"fileURL should not change after beginFetch has been invoked");
  3002. _bodyFileURL = fileURL;
  3003. }
  3004. } // @synchronized(self)
  3005. }
  3006. - (GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)bodyStreamProvider {
  3007. @synchronized(self) {
  3008. GTMSessionMonitorSynchronized(self);
  3009. return _bodyStreamProvider;
  3010. } // @synchronized(self)
  3011. }
  3012. - (void)setBodyStreamProvider:(GTM_NULLABLE GTMSessionFetcherBodyStreamProvider)block {
  3013. @synchronized(self) {
  3014. GTMSessionMonitorSynchronized(self);
  3015. GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized],
  3016. @"stream provider should not change after beginFetch has been invoked");
  3017. _bodyStreamProvider = [block copy];
  3018. } // @synchronized(self)
  3019. }
  3020. - (GTM_NULLABLE id<GTMFetcherAuthorizationProtocol>)authorizer {
  3021. @synchronized(self) {
  3022. GTMSessionMonitorSynchronized(self);
  3023. return _authorizer;
  3024. } // @synchronized(self)
  3025. }
  3026. - (void)setAuthorizer:(GTM_NULLABLE id<GTMFetcherAuthorizationProtocol>)authorizer {
  3027. @synchronized(self) {
  3028. GTMSessionMonitorSynchronized(self);
  3029. if (authorizer != _authorizer) {
  3030. if ([self isFetchingUnsynchronized]) {
  3031. GTMSESSION_ASSERT_DEBUG(0, @"authorizer should not change after beginFetch has been invoked");
  3032. } else {
  3033. _authorizer = authorizer;
  3034. }
  3035. }
  3036. } // @synchronized(self)
  3037. }
  3038. - (GTM_NULLABLE NSData *)downloadedData {
  3039. @synchronized(self) {
  3040. GTMSessionMonitorSynchronized(self);
  3041. return _downloadedData;
  3042. } // @synchronized(self)
  3043. }
  3044. - (void)setDownloadedData:(GTM_NULLABLE NSData *)data {
  3045. @synchronized(self) {
  3046. GTMSessionMonitorSynchronized(self);
  3047. _downloadedData = [data mutableCopy];
  3048. } // @synchronized(self)
  3049. }
  3050. - (int64_t)downloadedLength {
  3051. @synchronized(self) {
  3052. GTMSessionMonitorSynchronized(self);
  3053. return _downloadedLength;
  3054. } // @synchronized(self)
  3055. }
  3056. - (void)setDownloadedLength:(int64_t)length {
  3057. @synchronized(self) {
  3058. GTMSessionMonitorSynchronized(self);
  3059. _downloadedLength = length;
  3060. } // @synchronized(self)
  3061. }
  3062. - (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue {
  3063. @synchronized(self) {
  3064. GTMSessionMonitorSynchronized(self);
  3065. return _callbackQueue;
  3066. } // @synchronized(self)
  3067. }
  3068. - (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue {
  3069. @synchronized(self) {
  3070. GTMSessionMonitorSynchronized(self);
  3071. _callbackQueue = queue ?: dispatch_get_main_queue();
  3072. } // @synchronized(self)
  3073. }
  3074. - (GTM_NULLABLE NSURLSession *)session {
  3075. @synchronized(self) {
  3076. GTMSessionMonitorSynchronized(self);
  3077. return _session;
  3078. } // @synchronized(self)
  3079. }
  3080. - (NSInteger)servicePriority {
  3081. @synchronized(self) {
  3082. GTMSessionMonitorSynchronized(self);
  3083. return _servicePriority;
  3084. } // @synchronized(self)
  3085. }
  3086. - (void)setServicePriority:(NSInteger)value {
  3087. @synchronized(self) {
  3088. GTMSessionMonitorSynchronized(self);
  3089. if (value != _servicePriority) {
  3090. GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized],
  3091. @"servicePriority should not change after beginFetch has been invoked");
  3092. _servicePriority = value;
  3093. }
  3094. } // @synchronized(self)
  3095. }
  3096. - (void)setSession:(GTM_NULLABLE NSURLSession *)session {
  3097. @synchronized(self) {
  3098. GTMSessionMonitorSynchronized(self);
  3099. _session = session;
  3100. } // @synchronized(self)
  3101. }
  3102. - (BOOL)canShareSession {
  3103. @synchronized(self) {
  3104. GTMSessionMonitorSynchronized(self);
  3105. return _canShareSession;
  3106. } // @synchronized(self)
  3107. }
  3108. - (void)setCanShareSession:(BOOL)flag {
  3109. @synchronized(self) {
  3110. GTMSessionMonitorSynchronized(self);
  3111. _canShareSession = flag;
  3112. } // @synchronized(self)
  3113. }
  3114. - (BOOL)useBackgroundSession {
  3115. // This reflects if the user requested a background session, not necessarily
  3116. // if one was created. That is tracked with _usingBackgroundSession.
  3117. @synchronized(self) {
  3118. GTMSessionMonitorSynchronized(self);
  3119. return _userRequestedBackgroundSession;
  3120. } // @synchronized(self)
  3121. }
  3122. - (void)setUseBackgroundSession:(BOOL)flag {
  3123. @synchronized(self) {
  3124. GTMSessionMonitorSynchronized(self);
  3125. if (flag != _userRequestedBackgroundSession) {
  3126. GTMSESSION_ASSERT_DEBUG(![self isFetchingUnsynchronized],
  3127. @"useBackgroundSession should not change after beginFetch has been invoked");
  3128. _userRequestedBackgroundSession = flag;
  3129. }
  3130. } // @synchronized(self)
  3131. }
  3132. - (BOOL)isUsingBackgroundSession {
  3133. @synchronized(self) {
  3134. GTMSessionMonitorSynchronized(self);
  3135. return _usingBackgroundSession;
  3136. } // @synchronized(self)
  3137. }
  3138. - (void)setUsingBackgroundSession:(BOOL)flag {
  3139. @synchronized(self) {
  3140. GTMSessionMonitorSynchronized(self);
  3141. _usingBackgroundSession = flag;
  3142. } // @synchronized(self)
  3143. }
  3144. - (GTM_NULLABLE NSURLSession *)sessionNeedingInvalidation {
  3145. @synchronized(self) {
  3146. GTMSessionMonitorSynchronized(self);
  3147. return _sessionNeedingInvalidation;
  3148. } // @synchronized(self)
  3149. }
  3150. - (void)setSessionNeedingInvalidation:(GTM_NULLABLE NSURLSession *)session {
  3151. @synchronized(self) {
  3152. GTMSessionMonitorSynchronized(self);
  3153. _sessionNeedingInvalidation = session;
  3154. } // @synchronized(self)
  3155. }
  3156. - (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue {
  3157. @synchronized(self) {
  3158. GTMSessionMonitorSynchronized(self);
  3159. return _delegateQueue;
  3160. } // @synchronized(self)
  3161. }
  3162. - (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue {
  3163. @synchronized(self) {
  3164. GTMSessionMonitorSynchronized(self);
  3165. if (queue != _delegateQueue) {
  3166. if ([self isFetchingUnsynchronized]) {
  3167. GTMSESSION_ASSERT_DEBUG(0, @"sessionDelegateQueue should not change after fetch begins");
  3168. } else {
  3169. _delegateQueue = queue ?: [NSOperationQueue mainQueue];
  3170. }
  3171. }
  3172. } // @synchronized(self)
  3173. }
  3174. - (BOOL)userStoppedFetching {
  3175. @synchronized(self) {
  3176. GTMSessionMonitorSynchronized(self);
  3177. return _userStoppedFetching;
  3178. } // @synchronized(self)
  3179. }
  3180. - (GTM_NULLABLE id)userData {
  3181. @synchronized(self) {
  3182. GTMSessionMonitorSynchronized(self);
  3183. return _userData;
  3184. } // @synchronized(self)
  3185. }
  3186. - (void)setUserData:(GTM_NULLABLE id)theObj {
  3187. @synchronized(self) {
  3188. GTMSessionMonitorSynchronized(self);
  3189. _userData = theObj;
  3190. } // @synchronized(self)
  3191. }
  3192. - (GTM_NULLABLE NSURL *)destinationFileURL {
  3193. @synchronized(self) {
  3194. GTMSessionMonitorSynchronized(self);
  3195. return _destinationFileURL;
  3196. } // @synchronized(self)
  3197. }
  3198. - (void)setDestinationFileURL:(GTM_NULLABLE NSURL *)destinationFileURL {
  3199. @synchronized(self) {
  3200. GTMSessionMonitorSynchronized(self);
  3201. if (((_destinationFileURL == nil) && (destinationFileURL == nil)) ||
  3202. [_destinationFileURL isEqual:destinationFileURL]) {
  3203. return;
  3204. }
  3205. if (_sessionIdentifier) {
  3206. // This is something we don't expect to happen in production.
  3207. // However if it ever happen, leave a system log.
  3208. NSLog(@"%@: Destination File URL changed from (%@) to (%@) after session identifier has "
  3209. @"been created.",
  3210. [self class], _destinationFileURL, destinationFileURL);
  3211. #if DEBUG
  3212. // On both the simulator and devices, the path can change to the download file, but the name
  3213. // shouldn't change. Technically, this isn't supported in the fetcher, but the change of
  3214. // URL is expected to happen only across development runs through Xcode.
  3215. NSString *oldFilename = [_destinationFileURL lastPathComponent];
  3216. NSString *newFilename = [destinationFileURL lastPathComponent];
  3217. #pragma unused(oldFilename)
  3218. #pragma unused(newFilename)
  3219. GTMSESSION_ASSERT_DEBUG([oldFilename isEqualToString:newFilename],
  3220. @"Destination File URL cannot be changed after session identifier has been created");
  3221. #endif
  3222. }
  3223. _destinationFileURL = destinationFileURL;
  3224. } // @synchronized(self)
  3225. }
  3226. - (void)setProperties:(GTM_NULLABLE NSDictionary *)dict {
  3227. @synchronized(self) {
  3228. GTMSessionMonitorSynchronized(self);
  3229. _properties = [dict mutableCopy];
  3230. } // @synchronized(self)
  3231. }
  3232. - (GTM_NULLABLE NSDictionary *)properties {
  3233. @synchronized(self) {
  3234. GTMSessionMonitorSynchronized(self);
  3235. return _properties;
  3236. } // @synchronized(self)
  3237. }
  3238. - (void)setProperty:(GTM_NULLABLE id)obj forKey:(NSString *)key {
  3239. @synchronized(self) {
  3240. GTMSessionMonitorSynchronized(self);
  3241. if (_properties == nil && obj != nil) {
  3242. _properties = [[NSMutableDictionary alloc] init];
  3243. }
  3244. [_properties setValue:obj forKey:key];
  3245. } // @synchronized(self)
  3246. }
  3247. - (GTM_NULLABLE id)propertyForKey:(NSString *)key {
  3248. @synchronized(self) {
  3249. GTMSessionMonitorSynchronized(self);
  3250. return [_properties objectForKey:key];
  3251. } // @synchronized(self)
  3252. }
  3253. - (void)addPropertiesFromDictionary:(NSDictionary *)dict {
  3254. @synchronized(self) {
  3255. GTMSessionMonitorSynchronized(self);
  3256. if (_properties == nil && dict != nil) {
  3257. [self setProperties:[dict mutableCopy]];
  3258. } else {
  3259. [_properties addEntriesFromDictionary:dict];
  3260. }
  3261. } // @synchronized(self)
  3262. }
  3263. - (void)setCommentWithFormat:(id)format, ... {
  3264. #if !STRIP_GTM_FETCH_LOGGING
  3265. NSString *result = format;
  3266. if (format) {
  3267. va_list argList;
  3268. va_start(argList, format);
  3269. result = [[NSString alloc] initWithFormat:format
  3270. arguments:argList];
  3271. va_end(argList);
  3272. }
  3273. [self setComment:result];
  3274. #endif
  3275. }
  3276. #if !STRIP_GTM_FETCH_LOGGING
  3277. - (NSData *)loggedStreamData {
  3278. return _loggedStreamData;
  3279. }
  3280. - (void)appendLoggedStreamData:dataToAdd {
  3281. if (!_loggedStreamData) {
  3282. _loggedStreamData = [NSMutableData data];
  3283. }
  3284. [_loggedStreamData appendData:dataToAdd];
  3285. }
  3286. - (void)clearLoggedStreamData {
  3287. _loggedStreamData = nil;
  3288. }
  3289. - (void)setDeferResponseBodyLogging:(BOOL)deferResponseBodyLogging {
  3290. @synchronized(self) {
  3291. GTMSessionMonitorSynchronized(self);
  3292. if (deferResponseBodyLogging != _deferResponseBodyLogging) {
  3293. _deferResponseBodyLogging = deferResponseBodyLogging;
  3294. if (!deferResponseBodyLogging && !self.hasLoggedError) {
  3295. [_delegateQueue addOperationWithBlock:^{
  3296. [self logNowWithError:nil];
  3297. }];
  3298. }
  3299. }
  3300. } // @synchronized(self)
  3301. }
  3302. - (BOOL)deferResponseBodyLogging {
  3303. @synchronized(self) {
  3304. GTMSessionMonitorSynchronized(self);
  3305. return _deferResponseBodyLogging;
  3306. } // @synchronized(self)
  3307. }
  3308. #else
  3309. + (void)setLoggingEnabled:(BOOL)flag {
  3310. }
  3311. + (BOOL)isLoggingEnabled {
  3312. return NO;
  3313. }
  3314. #endif // STRIP_GTM_FETCH_LOGGING
  3315. @end
  3316. @implementation GTMSessionFetcher (BackwardsCompatibilityOnly)
  3317. - (void)setCookieStorageMethod:(NSInteger)method {
  3318. // For backwards compatibility with the old fetcher, we'll support the old constants.
  3319. //
  3320. // Clients using the GTMSessionFetcher class should set the cookie storage explicitly
  3321. // themselves.
  3322. NSHTTPCookieStorage *storage = nil;
  3323. switch(method) {
  3324. case 0: // kGTMHTTPFetcherCookieStorageMethodStatic
  3325. // nil storage will use [[self class] staticCookieStorage] when the fetch begins.
  3326. break;
  3327. case 1: // kGTMHTTPFetcherCookieStorageMethodFetchHistory
  3328. // Do nothing; use whatever was set by the fetcher service.
  3329. return;
  3330. case 2: // kGTMHTTPFetcherCookieStorageMethodSystemDefault
  3331. storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  3332. break;
  3333. case 3: // kGTMHTTPFetcherCookieStorageMethodNone
  3334. // Create temporary storage for this fetcher only.
  3335. storage = [[GTMSessionCookieStorage alloc] init];
  3336. break;
  3337. default:
  3338. GTMSESSION_ASSERT_DEBUG(0, @"Invalid cookie storage method: %d", (int)method);
  3339. }
  3340. self.cookieStorage = storage;
  3341. }
  3342. @end
  3343. @implementation GTMSessionCookieStorage {
  3344. NSMutableArray *_cookies;
  3345. NSHTTPCookieAcceptPolicy _policy;
  3346. }
  3347. - (id)init {
  3348. self = [super init];
  3349. if (self != nil) {
  3350. _cookies = [[NSMutableArray alloc] init];
  3351. }
  3352. return self;
  3353. }
  3354. - (GTM_NULLABLE NSArray *)cookies {
  3355. @synchronized(self) {
  3356. GTMSessionMonitorSynchronized(self);
  3357. return [_cookies copy];
  3358. } // @synchronized(self)
  3359. }
  3360. - (void)setCookie:(NSHTTPCookie *)cookie {
  3361. if (!cookie) return;
  3362. if (_policy == NSHTTPCookieAcceptPolicyNever) return;
  3363. @synchronized(self) {
  3364. GTMSessionMonitorSynchronized(self);
  3365. [self internalSetCookie:cookie];
  3366. } // @synchronized(self)
  3367. }
  3368. // Note: this should only be called from inside a @synchronized(self) block.
  3369. - (void)internalSetCookie:(NSHTTPCookie *)newCookie {
  3370. GTMSessionCheckSynchronized(self);
  3371. if (_policy == NSHTTPCookieAcceptPolicyNever) return;
  3372. BOOL isValidCookie = (newCookie.name.length > 0
  3373. && newCookie.domain.length > 0
  3374. && newCookie.path.length > 0);
  3375. GTMSESSION_ASSERT_DEBUG(isValidCookie, @"invalid cookie: %@", newCookie);
  3376. if (isValidCookie) {
  3377. // Remove the cookie if it's currently in the array.
  3378. NSHTTPCookie *oldCookie = [self cookieMatchingCookie:newCookie];
  3379. if (oldCookie) {
  3380. [_cookies removeObjectIdenticalTo:oldCookie];
  3381. }
  3382. if (![[self class] hasCookieExpired:newCookie]) {
  3383. [_cookies addObject:newCookie];
  3384. }
  3385. }
  3386. }
  3387. // Add all cookies in the new cookie array to the storage,
  3388. // replacing stored cookies as appropriate.
  3389. //
  3390. // Side effect: removes expired cookies from the storage array.
  3391. - (void)setCookies:(GTM_NULLABLE NSArray *)newCookies {
  3392. @synchronized(self) {
  3393. GTMSessionMonitorSynchronized(self);
  3394. [self removeExpiredCookies];
  3395. for (NSHTTPCookie *newCookie in newCookies) {
  3396. [self internalSetCookie:newCookie];
  3397. }
  3398. } // @synchronized(self)
  3399. }
  3400. - (void)setCookies:(NSArray *)cookies forURL:(GTM_NULLABLE NSURL *)URL mainDocumentURL:(GTM_NULLABLE NSURL *)mainDocumentURL {
  3401. @synchronized(self) {
  3402. GTMSessionMonitorSynchronized(self);
  3403. if (_policy == NSHTTPCookieAcceptPolicyNever) {
  3404. return;
  3405. }
  3406. if (_policy == NSHTTPCookieAcceptPolicyOnlyFromMainDocumentDomain) {
  3407. NSString *mainHost = mainDocumentURL.host;
  3408. NSString *associatedHost = URL.host;
  3409. if (!mainHost || ![associatedHost hasSuffix:mainHost]) {
  3410. return;
  3411. }
  3412. }
  3413. } // @synchronized(self)
  3414. [self setCookies:cookies];
  3415. }
  3416. - (void)deleteCookie:(NSHTTPCookie *)cookie {
  3417. if (!cookie) return;
  3418. @synchronized(self) {
  3419. GTMSessionMonitorSynchronized(self);
  3420. NSHTTPCookie *foundCookie = [self cookieMatchingCookie:cookie];
  3421. if (foundCookie) {
  3422. [_cookies removeObjectIdenticalTo:foundCookie];
  3423. }
  3424. } // @synchronized(self)
  3425. }
  3426. // Retrieve all cookies appropriate for the given URL, considering
  3427. // domain, path, cookie name, expiration, security setting.
  3428. // Side effect: removed expired cookies from the storage array.
  3429. - (GTM_NULLABLE NSArray *)cookiesForURL:(NSURL *)theURL {
  3430. NSMutableArray *foundCookies = nil;
  3431. @synchronized(self) {
  3432. GTMSessionMonitorSynchronized(self);
  3433. [self removeExpiredCookies];
  3434. // We'll prepend "." to the desired domain, since we want the
  3435. // actual domain "nytimes.com" to still match the cookie domain
  3436. // ".nytimes.com" when we check it below with hasSuffix.
  3437. NSString *host = theURL.host.lowercaseString;
  3438. NSString *path = theURL.path;
  3439. NSString *scheme = [theURL scheme];
  3440. NSString *requestingDomain = nil;
  3441. BOOL isLocalhostRetrieval = NO;
  3442. if (IsLocalhost(host)) {
  3443. isLocalhostRetrieval = YES;
  3444. } else {
  3445. if (host.length > 0) {
  3446. requestingDomain = [@"." stringByAppendingString:host];
  3447. }
  3448. }
  3449. for (NSHTTPCookie *storedCookie in _cookies) {
  3450. NSString *cookieDomain = storedCookie.domain.lowercaseString;
  3451. NSString *cookiePath = storedCookie.path;
  3452. BOOL cookieIsSecure = [storedCookie isSecure];
  3453. BOOL isDomainOK;
  3454. if (isLocalhostRetrieval) {
  3455. // Prior to 10.5.6, the domain stored into NSHTTPCookies for localhost
  3456. // is "localhost.local"
  3457. isDomainOK = (IsLocalhost(cookieDomain)
  3458. || [cookieDomain isEqual:@"localhost.local"]);
  3459. } else {
  3460. // Ensure we're matching exact domain names. We prepended a dot to the
  3461. // requesting domain, so we can also prepend one here if needed before
  3462. // checking if the request contains the cookie domain.
  3463. if (![cookieDomain hasPrefix:@"."]) {
  3464. cookieDomain = [@"." stringByAppendingString:cookieDomain];
  3465. }
  3466. isDomainOK = [requestingDomain hasSuffix:cookieDomain];
  3467. }
  3468. BOOL isPathOK = [cookiePath isEqual:@"/"] || [path hasPrefix:cookiePath];
  3469. BOOL isSecureOK = (!cookieIsSecure
  3470. || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame);
  3471. if (isDomainOK && isPathOK && isSecureOK) {
  3472. if (foundCookies == nil) {
  3473. foundCookies = [NSMutableArray array];
  3474. }
  3475. [foundCookies addObject:storedCookie];
  3476. }
  3477. }
  3478. } // @synchronized(self)
  3479. return foundCookies;
  3480. }
  3481. // Override methods from the NSHTTPCookieStorage (NSURLSessionTaskAdditions) category.
  3482. - (void)storeCookies:(NSArray *)cookies forTask:(NSURLSessionTask *)task {
  3483. NSURLRequest *currentRequest = task.currentRequest;
  3484. [self setCookies:cookies forURL:currentRequest.URL mainDocumentURL:nil];
  3485. }
  3486. - (void)getCookiesForTask:(NSURLSessionTask *)task
  3487. completionHandler:(void (^)(GTM_NSArrayOf(NSHTTPCookie *) *))completionHandler {
  3488. if (completionHandler) {
  3489. NSURLRequest *currentRequest = task.currentRequest;
  3490. NSURL *currentRequestURL = currentRequest.URL;
  3491. NSArray *cookies = [self cookiesForURL:currentRequestURL];
  3492. completionHandler(cookies);
  3493. }
  3494. }
  3495. // Return a cookie from the array with the same name, domain, and path as the
  3496. // given cookie, or else return nil if none found.
  3497. //
  3498. // Both the cookie being tested and all cookies in the storage array should
  3499. // be valid (non-nil name, domains, paths).
  3500. //
  3501. // Note: this should only be called from inside a @synchronized(self) block
  3502. - (GTM_NULLABLE NSHTTPCookie *)cookieMatchingCookie:(NSHTTPCookie *)cookie {
  3503. GTMSessionCheckSynchronized(self);
  3504. NSString *name = cookie.name;
  3505. NSString *domain = cookie.domain;
  3506. NSString *path = cookie.path;
  3507. GTMSESSION_ASSERT_DEBUG(name && domain && path,
  3508. @"Invalid stored cookie (name:%@ domain:%@ path:%@)", name, domain, path);
  3509. for (NSHTTPCookie *storedCookie in _cookies) {
  3510. if ([storedCookie.name isEqual:name]
  3511. && [storedCookie.domain isEqual:domain]
  3512. && [storedCookie.path isEqual:path]) {
  3513. return storedCookie;
  3514. }
  3515. }
  3516. return nil;
  3517. }
  3518. // Internal routine to remove any expired cookies from the array, excluding
  3519. // cookies with nil expirations.
  3520. //
  3521. // Note: this should only be called from inside a @synchronized(self) block
  3522. - (void)removeExpiredCookies {
  3523. GTMSessionCheckSynchronized(self);
  3524. // Count backwards since we're deleting items from the array
  3525. for (NSInteger idx = (NSInteger)_cookies.count - 1; idx >= 0; idx--) {
  3526. NSHTTPCookie *storedCookie = [_cookies objectAtIndex:(NSUInteger)idx];
  3527. if ([[self class] hasCookieExpired:storedCookie]) {
  3528. [_cookies removeObjectAtIndex:(NSUInteger)idx];
  3529. }
  3530. }
  3531. }
  3532. + (BOOL)hasCookieExpired:(NSHTTPCookie *)cookie {
  3533. NSDate *expiresDate = [cookie expiresDate];
  3534. if (expiresDate == nil) {
  3535. // Cookies seem to have a Expires property even when the expiresDate method returns nil.
  3536. id expiresVal = [[cookie properties] objectForKey:NSHTTPCookieExpires];
  3537. if ([expiresVal isKindOfClass:[NSDate class]]) {
  3538. expiresDate = expiresVal;
  3539. }
  3540. }
  3541. BOOL hasExpired = (expiresDate != nil && [expiresDate timeIntervalSinceNow] < 0);
  3542. return hasExpired;
  3543. }
  3544. - (void)removeAllCookies {
  3545. @synchronized(self) {
  3546. GTMSessionMonitorSynchronized(self);
  3547. [_cookies removeAllObjects];
  3548. } // @synchronized(self)
  3549. }
  3550. - (NSHTTPCookieAcceptPolicy)cookieAcceptPolicy {
  3551. @synchronized(self) {
  3552. GTMSessionMonitorSynchronized(self);
  3553. return _policy;
  3554. } // @synchronized(self)
  3555. }
  3556. - (void)setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicy)cookieAcceptPolicy {
  3557. @synchronized(self) {
  3558. GTMSessionMonitorSynchronized(self);
  3559. _policy = cookieAcceptPolicy;
  3560. } // @synchronized(self)
  3561. }
  3562. @end
  3563. void GTMSessionFetcherAssertValidSelector(id GTM_NULLABLE_TYPE obj, SEL GTM_NULLABLE_TYPE sel, ...) {
  3564. // Verify that the object's selector is implemented with the proper
  3565. // number and type of arguments
  3566. #if DEBUG
  3567. va_list argList;
  3568. va_start(argList, sel);
  3569. if (obj && sel) {
  3570. // Check that the selector is implemented
  3571. if (![obj respondsToSelector:sel]) {
  3572. NSLog(@"\"%@\" selector \"%@\" is unimplemented or misnamed",
  3573. NSStringFromClass([(id)obj class]),
  3574. NSStringFromSelector((SEL)sel));
  3575. NSCAssert(0, @"callback selector unimplemented or misnamed");
  3576. } else {
  3577. const char *expectedArgType;
  3578. unsigned int argCount = 2; // skip self and _cmd
  3579. NSMethodSignature *sig = [obj methodSignatureForSelector:sel];
  3580. // Check that each expected argument is present and of the correct type
  3581. while ((expectedArgType = va_arg(argList, const char*)) != 0) {
  3582. if ([sig numberOfArguments] > argCount) {
  3583. const char *foundArgType = [sig getArgumentTypeAtIndex:argCount];
  3584. if (0 != strncmp(foundArgType, expectedArgType, strlen(expectedArgType))) {
  3585. NSLog(@"\"%@\" selector \"%@\" argument %d should be type %s",
  3586. NSStringFromClass([(id)obj class]),
  3587. NSStringFromSelector((SEL)sel), (argCount - 2), expectedArgType);
  3588. NSCAssert(0, @"callback selector argument type mistake");
  3589. }
  3590. }
  3591. argCount++;
  3592. }
  3593. // Check that the proper number of arguments are present in the selector
  3594. if (argCount != [sig numberOfArguments]) {
  3595. NSLog(@"\"%@\" selector \"%@\" should have %d arguments",
  3596. NSStringFromClass([(id)obj class]),
  3597. NSStringFromSelector((SEL)sel), (argCount - 2));
  3598. NSCAssert(0, @"callback selector arguments incorrect");
  3599. }
  3600. }
  3601. }
  3602. va_end(argList);
  3603. #endif
  3604. }
  3605. NSString *GTMFetcherCleanedUserAgentString(NSString *str) {
  3606. // Reference http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html
  3607. // and http://www-archive.mozilla.org/build/user-agent-strings.html
  3608. if (str == nil) return @"";
  3609. NSMutableString *result = [NSMutableString stringWithString:str];
  3610. // Replace spaces and commas with underscores
  3611. [result replaceOccurrencesOfString:@" "
  3612. withString:@"_"
  3613. options:0
  3614. range:NSMakeRange(0, result.length)];
  3615. [result replaceOccurrencesOfString:@","
  3616. withString:@"_"
  3617. options:0
  3618. range:NSMakeRange(0, result.length)];
  3619. // Delete http token separators and remaining whitespace
  3620. static NSCharacterSet *charsToDelete = nil;
  3621. if (charsToDelete == nil) {
  3622. // Make a set of unwanted characters
  3623. NSString *const kSeparators = @"()<>@;:\\\"/[]?={}";
  3624. NSMutableCharacterSet *mutableChars =
  3625. [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
  3626. [mutableChars addCharactersInString:kSeparators];
  3627. charsToDelete = [mutableChars copy]; // hang on to an immutable copy
  3628. }
  3629. while (1) {
  3630. NSRange separatorRange = [result rangeOfCharacterFromSet:charsToDelete];
  3631. if (separatorRange.location == NSNotFound) break;
  3632. [result deleteCharactersInRange:separatorRange];
  3633. };
  3634. return result;
  3635. }
  3636. NSString *GTMFetcherSystemVersionString(void) {
  3637. static NSString *sSavedSystemString;
  3638. static dispatch_once_t onceToken;
  3639. dispatch_once(&onceToken, ^{
  3640. #if TARGET_OS_MAC && !TARGET_OS_IPHONE
  3641. // Mac build
  3642. NSProcessInfo *procInfo = [NSProcessInfo processInfo];
  3643. #if !defined(MAC_OS_X_VERSION_10_10)
  3644. BOOL hasOperatingSystemVersion = NO;
  3645. #elif MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10
  3646. BOOL hasOperatingSystemVersion =
  3647. [procInfo respondsToSelector:@selector(operatingSystemVersion)];
  3648. #else
  3649. BOOL hasOperatingSystemVersion = YES;
  3650. #endif
  3651. NSString *versString;
  3652. if (hasOperatingSystemVersion) {
  3653. #if defined(MAC_OS_X_VERSION_10_10)
  3654. // A reference to NSOperatingSystemVersion requires the 10.10 SDK.
  3655. NSOperatingSystemVersion version = procInfo.operatingSystemVersion;
  3656. versString = [NSString stringWithFormat:@"%zd.%zd.%zd",
  3657. version.majorVersion, version.minorVersion, version.patchVersion];
  3658. #else
  3659. #pragma unused(procInfo)
  3660. #endif
  3661. } else {
  3662. // With Gestalt inexplicably deprecated in 10.8, we're reduced to reading
  3663. // the system plist file.
  3664. NSString *const kPath = @"/System/Library/CoreServices/SystemVersion.plist";
  3665. NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:kPath];
  3666. versString = [plist objectForKey:@"ProductVersion"];
  3667. if (versString.length == 0) {
  3668. versString = @"10.?.?";
  3669. }
  3670. }
  3671. sSavedSystemString = [[NSString alloc] initWithFormat:@"MacOSX/%@", versString];
  3672. #elif TARGET_OS_IPHONE
  3673. // Compiling against the iPhone SDK
  3674. // Avoid the slowness of calling currentDevice repeatedly on the iPhone
  3675. UIDevice* currentDevice = [UIDevice currentDevice];
  3676. NSString *rawModel = [currentDevice model];
  3677. NSString *model = GTMFetcherCleanedUserAgentString(rawModel);
  3678. NSString *systemVersion = [currentDevice systemVersion];
  3679. #if TARGET_IPHONE_SIMULATOR
  3680. NSString *hardwareModel = @"sim";
  3681. #else
  3682. NSString *hardwareModel;
  3683. struct utsname unameRecord;
  3684. if (uname(&unameRecord) == 0) {
  3685. NSString *machineName = [NSString stringWithCString:unameRecord.machine
  3686. encoding:NSUTF8StringEncoding];
  3687. hardwareModel = GTMFetcherCleanedUserAgentString(machineName);
  3688. } else {
  3689. hardwareModel = @"unk";
  3690. }
  3691. #endif
  3692. sSavedSystemString = [[NSString alloc] initWithFormat:@"%@/%@ hw/%@",
  3693. model, systemVersion, hardwareModel];
  3694. // Example: iPod_Touch/2.2 hw/iPod1_1
  3695. #elif defined(_SYS_UTSNAME_H)
  3696. // Foundation-only build
  3697. struct utsname unameRecord;
  3698. uname(&unameRecord);
  3699. sSavedSystemString = [NSString stringWithFormat:@"%s/%s",
  3700. unameRecord.sysname, unameRecord.release]; // "Darwin/8.11.1"
  3701. #endif
  3702. });
  3703. return sSavedSystemString;
  3704. }
  3705. NSString *GTMFetcherStandardUserAgentString(NSBundle * GTM_NULLABLE_TYPE bundle) {
  3706. NSString *result = [NSString stringWithFormat:@"%@ %@",
  3707. GTMFetcherApplicationIdentifier(bundle),
  3708. GTMFetcherSystemVersionString()];
  3709. return result;
  3710. }
  3711. NSString *GTMFetcherApplicationIdentifier(NSBundle * GTM_NULLABLE_TYPE bundle) {
  3712. @synchronized([GTMSessionFetcher class]) {
  3713. static NSMutableDictionary *sAppIDMap = nil;
  3714. // If there's a bundle ID, use that; otherwise, use the process name
  3715. if (bundle == nil) {
  3716. bundle = [NSBundle mainBundle];
  3717. }
  3718. NSString *bundleID = [bundle bundleIdentifier];
  3719. if (bundleID == nil) {
  3720. bundleID = @"";
  3721. }
  3722. NSString *identifier = [sAppIDMap objectForKey:bundleID];
  3723. if (identifier) return identifier;
  3724. // Apps may add a string to the info.plist to uniquely identify different builds.
  3725. identifier = [bundle objectForInfoDictionaryKey:@"GTMUserAgentID"];
  3726. if (identifier.length == 0) {
  3727. if (bundleID.length > 0) {
  3728. identifier = bundleID;
  3729. } else {
  3730. // Fall back on the procname, prefixed by "proc" to flag that it's
  3731. // autogenerated and perhaps unreliable
  3732. NSString *procName = [[NSProcessInfo processInfo] processName];
  3733. identifier = [NSString stringWithFormat:@"proc_%@", procName];
  3734. }
  3735. }
  3736. // Clean up whitespace and special characters
  3737. identifier = GTMFetcherCleanedUserAgentString(identifier);
  3738. // If there's a version number, append that
  3739. NSString *version = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
  3740. if (version.length == 0) {
  3741. version = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"];
  3742. }
  3743. // Clean up whitespace and special characters
  3744. version = GTMFetcherCleanedUserAgentString(version);
  3745. // Glue the two together (cleanup done above or else cleanup would strip the
  3746. // slash)
  3747. if (version.length > 0) {
  3748. identifier = [identifier stringByAppendingFormat:@"/%@", version];
  3749. }
  3750. if (sAppIDMap == nil) {
  3751. sAppIDMap = [[NSMutableDictionary alloc] init];
  3752. }
  3753. [sAppIDMap setObject:identifier forKey:bundleID];
  3754. return identifier;
  3755. }
  3756. }
  3757. #if DEBUG
  3758. @implementation GTMSessionSyncMonitorInternal {
  3759. NSValue *_objectKey; // The synchronize target object.
  3760. const char *_functionName; // The function containing the monitored sync block.
  3761. }
  3762. - (instancetype)initWithSynchronizationObject:(id)object
  3763. allowRecursive:(BOOL)allowRecursive
  3764. functionName:(const char *)functionName {
  3765. self = [super init];
  3766. if (self) {
  3767. Class threadKey = [GTMSessionSyncMonitorInternal class];
  3768. _objectKey = [NSValue valueWithNonretainedObject:object];
  3769. _functionName = functionName;
  3770. NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary;
  3771. NSMutableDictionary *counters = threadDict[threadKey];
  3772. if (counters == nil) {
  3773. counters = [NSMutableDictionary dictionary];
  3774. threadDict[(id)threadKey] = counters;
  3775. }
  3776. NSCountedSet *functionNamesCounter = counters[_objectKey];
  3777. NSUInteger numberOfSyncingFunctions = functionNamesCounter.count;
  3778. if (!allowRecursive) {
  3779. BOOL isTopLevelSyncScope = (numberOfSyncingFunctions == 0);
  3780. NSArray *stack = [NSThread callStackSymbols];
  3781. GTMSESSION_ASSERT_DEBUG(isTopLevelSyncScope,
  3782. @"*** Recursive sync on %@ at %s; previous sync at %@\n%@",
  3783. [object class], functionName, functionNamesCounter.allObjects,
  3784. [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]);
  3785. }
  3786. if (!functionNamesCounter) {
  3787. functionNamesCounter = [NSCountedSet set];
  3788. counters[_objectKey] = functionNamesCounter;
  3789. }
  3790. [functionNamesCounter addObject:@(functionName)];
  3791. }
  3792. return self;
  3793. }
  3794. - (void)dealloc {
  3795. Class threadKey = [GTMSessionSyncMonitorInternal class];
  3796. NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary;
  3797. NSMutableDictionary *counters = threadDict[threadKey];
  3798. NSCountedSet *functionNamesCounter = counters[_objectKey];
  3799. NSString *functionNameStr = @(_functionName);
  3800. NSUInteger numberOfSyncsByThisFunction = [functionNamesCounter countForObject:functionNameStr];
  3801. NSArray *stack = [NSThread callStackSymbols];
  3802. GTMSESSION_ASSERT_DEBUG(numberOfSyncsByThisFunction > 0, @"Sync not found on %@ at %s\n%@",
  3803. [_objectKey.nonretainedObjectValue class], _functionName,
  3804. [stack subarrayWithRange:NSMakeRange(1, stack.count - 1)]);
  3805. [functionNamesCounter removeObject:functionNameStr];
  3806. if (functionNamesCounter.count == 0) {
  3807. [counters removeObjectForKey:_objectKey];
  3808. }
  3809. }
  3810. + (NSArray *)functionsHoldingSynchronizationOnObject:(id)object {
  3811. Class threadKey = [GTMSessionSyncMonitorInternal class];
  3812. NSValue *localObjectKey = [NSValue valueWithNonretainedObject:object];
  3813. NSMutableDictionary *threadDict = [NSThread currentThread].threadDictionary;
  3814. NSMutableDictionary *counters = threadDict[threadKey];
  3815. NSCountedSet *functionNamesCounter = counters[localObjectKey];
  3816. return functionNamesCounter.count > 0 ? functionNamesCounter.allObjects : nil;
  3817. }
  3818. @end
  3819. #endif // DEBUG
  3820. GTM_ASSUME_NONNULL_END