You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4571 lines
167 KiB

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