FIRNetworkURLSession.m 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. // Copyright 2017 Google
  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. #import <Foundation/Foundation.h>
  15. #import "Private/FIRNetworkURLSession.h"
  16. #import "Private/FIRMutableDictionary.h"
  17. #import "Private/FIRNetworkConstants.h"
  18. #import "Private/FIRNetworkMessageCode.h"
  19. #import "Private/FIRLogger.h"
  20. @implementation FIRNetworkURLSession {
  21. /// The handler to be called when the request completes or error has occurs.
  22. FIRNetworkURLSessionCompletionHandler _completionHandler;
  23. /// Session ID generated randomly with a fixed prefix.
  24. NSString *_sessionID;
  25. /// The session configuration.
  26. NSURLSessionConfiguration *_sessionConfig;
  27. /// The path to the directory where all temporary files are stored before uploading.
  28. NSURL *_networkDirectoryURL;
  29. /// The downloaded data from fetching.
  30. NSData *_downloadedData;
  31. /// The path to the temporary file which stores the uploading data.
  32. NSURL *_uploadingFileURL;
  33. /// The current request.
  34. NSURLRequest *_request;
  35. }
  36. #pragma mark - Init
  37. - (instancetype)initWithNetworkLoggerDelegate:(id<FIRNetworkLoggerDelegate>)networkLoggerDelegate {
  38. self = [super init];
  39. if (self) {
  40. // Create URL to the directory where all temporary files to upload have to be stored.
  41. NSArray *paths =
  42. NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
  43. NSString *applicationSupportDirectory = paths.firstObject;
  44. NSArray *tempPathComponents = @[
  45. applicationSupportDirectory,
  46. kFIRNetworkApplicationSupportSubdirectory,
  47. kFIRNetworkTempDirectoryName
  48. ];
  49. _networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents];
  50. _sessionID = [NSString stringWithFormat:@"%@-%@", kFIRNetworkBackgroundSessionConfigIDPrefix,
  51. [[NSUUID UUID] UUIDString]];
  52. _loggerDelegate = networkLoggerDelegate;
  53. }
  54. return self;
  55. }
  56. #pragma mark - External Methods
  57. #pragma mark - To be called from AppDelegate
  58. + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
  59. completionHandler:
  60. (FIRNetworkSystemCompletionHandler)systemCompletionHandler {
  61. // The session may not be FIRAnalytics background. Ignore those that do not have the prefix.
  62. if (![sessionID hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) {
  63. return;
  64. }
  65. FIRNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID];
  66. if (fetcher != nil) {
  67. [fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID];
  68. } else {
  69. FIRLogError(kFIRLoggerCore,
  70. [NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork003],
  71. @"Failed to retrieve background session with ID %@ after app is relaunched.",
  72. sessionID);
  73. }
  74. }
  75. #pragma mark - External Methods
  76. /// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the
  77. /// connection.
  78. - (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
  79. completionHandler:(FIRNetworkURLSessionCompletionHandler)handler {
  80. // NSURLSessionUploadTask does not work with NSData in the background.
  81. // To avoid this issue, write the data to a temporary file to upload it.
  82. // Make a temporary file with the data subset.
  83. _uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID];
  84. NSError *writeError;
  85. NSURLSessionUploadTask *postRequestTask;
  86. NSURLSession *session;
  87. BOOL didWriteFile = NO;
  88. // Clean up the entire temp folder to avoid temp files that remain in case the previous session
  89. // crashed and did not clean up.
  90. [self maybeRemoveTempFilesAtURL:_networkDirectoryURL
  91. expiringTime:kFIRNetworkTempFolderExpireTime];
  92. // If there is no background network enabled, no need to write to file. This will allow default
  93. // network session which runs on the foreground.
  94. if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) {
  95. didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path
  96. options:NSDataWritingAtomic
  97. error:&writeError];
  98. if (writeError) {
  99. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
  100. messageCode:kFIRNetworkMessageCodeURLSession000
  101. message:@"Failed to write request data to file"
  102. context:writeError];
  103. }
  104. }
  105. if (didWriteFile) {
  106. // Exclude this file from backing up to iTunes. There are conflicting reports that excluding
  107. // directory from backing up does not excluding files of that directory from backing up.
  108. [self excludeFromBackupForURL:_uploadingFileURL];
  109. _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
  110. [self populateSessionConfig:_sessionConfig withRequest:request];
  111. session = [NSURLSession sessionWithConfiguration:_sessionConfig
  112. delegate:self
  113. delegateQueue:[NSOperationQueue mainQueue]];
  114. postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL];
  115. } else {
  116. // If we cannot write to file, just send it in the foreground.
  117. _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
  118. [self populateSessionConfig:_sessionConfig withRequest:request];
  119. _sessionConfig.URLCache = nil;
  120. session = [NSURLSession sessionWithConfiguration:_sessionConfig
  121. delegate:self
  122. delegateQueue:[NSOperationQueue mainQueue]];
  123. postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody];
  124. }
  125. if (!session || !postRequestTask) {
  126. NSError *error =
  127. [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain
  128. code:FIRErrorCodeNetworkRequestCreation
  129. userInfo:@{
  130. kFIRNetworkErrorContext : @"Cannot create network session"
  131. }];
  132. [self callCompletionHandler:handler withResponse:nil data:nil error:error];
  133. return nil;
  134. }
  135. // Save the session into memory.
  136. NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
  137. [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
  138. _request = [request copy];
  139. // Store completion handler because background session does not accept handler block but custom
  140. // delegate.
  141. _completionHandler = [handler copy];
  142. [postRequestTask resume];
  143. return _sessionID;
  144. }
  145. /// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session.
  146. - (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
  147. completionHandler:(FIRNetworkURLSessionCompletionHandler)handler {
  148. if (_backgroundNetworkEnabled) {
  149. _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
  150. } else {
  151. _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
  152. }
  153. [self populateSessionConfig:_sessionConfig withRequest:request];
  154. // Do not cache the GET request.
  155. _sessionConfig.URLCache = nil;
  156. NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig
  157. delegate:self
  158. delegateQueue:[NSOperationQueue mainQueue]];
  159. NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
  160. if (!session || !downloadTask) {
  161. NSError *error =
  162. [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain
  163. code:FIRErrorCodeNetworkRequestCreation
  164. userInfo:@{
  165. kFIRNetworkErrorContext : @"Cannot create network session"
  166. }];
  167. [self callCompletionHandler:handler withResponse:nil data:nil error:error];
  168. return nil;
  169. }
  170. // Save the session into memory.
  171. NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
  172. [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
  173. _request = [request copy];
  174. _completionHandler = [handler copy];
  175. [downloadTask resume];
  176. return _sessionID;
  177. }
  178. #pragma mark - NSURLSessionTaskDelegate
  179. /// Called by the NSURLSession once the download task is completed. The file is saved in the
  180. /// provided URL so we need to read the data and store into _downloadedData. Once the session is
  181. /// completed, URLSession:task:didCompleteWithError will be called and the completion handler will
  182. /// be called with the downloaded data.
  183. - (void)URLSession:(NSURLSession *)session
  184. downloadTask:(NSURLSessionDownloadTask *)task
  185. didFinishDownloadingToURL:(NSURL *)url {
  186. if (!url.path) {
  187. [_loggerDelegate
  188. firNetwork_logWithLevel:kFIRNetworkLogLevelError
  189. messageCode:kFIRNetworkMessageCodeURLSession001
  190. message:@"Unable to read downloaded data from empty temp path"];
  191. _downloadedData = nil;
  192. return;
  193. }
  194. NSError *error;
  195. _downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error];
  196. if (error) {
  197. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
  198. messageCode:kFIRNetworkMessageCodeURLSession002
  199. message:@"Cannot read the content of downloaded data"
  200. context:error];
  201. _downloadedData = nil;
  202. }
  203. }
  204. - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
  205. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  206. messageCode:kFIRNetworkMessageCodeURLSession003
  207. message:@"Background session finished"
  208. context:session.configuration.identifier];
  209. [self callSystemCompletionHandler:session.configuration.identifier];
  210. }
  211. - (void)URLSession:(NSURLSession *)session
  212. task:(NSURLSessionTask *)task
  213. didCompleteWithError:(NSError *)error {
  214. // Avoid any chance of recursive behavior leading to it being used repeatedly.
  215. FIRNetworkURLSessionCompletionHandler handler = _completionHandler;
  216. _completionHandler = nil;
  217. if (task.response) {
  218. // The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7.
  219. NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP");
  220. // The server responded so ignore the error created by the system.
  221. error = nil;
  222. } else if (!error) {
  223. error =
  224. [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain
  225. code:FIRErrorCodeNetworkInvalidResponse
  226. userInfo:@{
  227. kFIRNetworkErrorContext : @"Network Error: Empty network response"
  228. }];
  229. }
  230. [self callCompletionHandler:handler
  231. withResponse:(NSHTTPURLResponse *)task.response
  232. data:_downloadedData
  233. error:error];
  234. // Remove the temp file to avoid trashing devices with lots of temp files.
  235. [self removeTempItemAtURL:_uploadingFileURL];
  236. // Try to clean up stale files again.
  237. [self maybeRemoveTempFilesAtURL:_networkDirectoryURL
  238. expiringTime:kFIRNetworkTempFolderExpireTime];
  239. }
  240. - (void)URLSession:(NSURLSession *)session
  241. task:(NSURLSessionTask *)task
  242. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  243. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
  244. NSURLCredential *credential))completionHandler {
  245. // The handling is modeled after GTMSessionFetcher.
  246. if ([challenge.protectionSpace.authenticationMethod
  247. isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  248. SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
  249. if (serverTrust == NULL) {
  250. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  251. messageCode:kFIRNetworkMessageCodeURLSession004
  252. message:@"Received empty server trust for host. Host"
  253. context:_request.URL];
  254. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  255. return;
  256. }
  257. NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
  258. if (!credential) {
  259. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
  260. messageCode:kFIRNetworkMessageCodeURLSession005
  261. message:@"Unable to verify server identity. Host"
  262. context:_request.URL];
  263. completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  264. return;
  265. }
  266. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  267. messageCode:kFIRNetworkMessageCodeURLSession006
  268. message:@"Received SSL challenge for host. Host"
  269. context:_request.URL];
  270. void (^callback)(BOOL) = ^(BOOL allow) {
  271. if (allow) {
  272. completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  273. } else {
  274. [_loggerDelegate
  275. firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  276. messageCode:kFIRNetworkMessageCodeURLSession007
  277. message:@"Cancelling authentication challenge for host. Host"
  278. context:_request.URL];
  279. completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
  280. }
  281. };
  282. // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7.
  283. CFRetain(serverTrust);
  284. // Evaluate the certificate chain.
  285. //
  286. // The delegate queue may be the main thread. Trust evaluation could cause some
  287. // blocking network activity, so we must evaluate async, as documented at
  288. // https://developer.apple.com/library/ios/technotes/tn2232/
  289. dispatch_queue_t evaluateBackgroundQueue =
  290. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  291. dispatch_async(evaluateBackgroundQueue, ^{
  292. SecTrustResultType trustEval = kSecTrustResultInvalid;
  293. BOOL shouldAllow;
  294. OSStatus trustError;
  295. @synchronized([FIRNetworkURLSession class]) {
  296. trustError = SecTrustEvaluate(serverTrust, &trustEval);
  297. }
  298. if (trustError != errSecSuccess) {
  299. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
  300. messageCode:kFIRNetworkMessageCodeURLSession008
  301. message:@"Cannot evaluate server trust. Error, host"
  302. contexts:@[ @(trustError), _request.URL ]];
  303. shouldAllow = NO;
  304. } else {
  305. // Having a trust level "unspecified" by the user is the usual result, described at
  306. // https://developer.apple.com/library/mac/qa/qa1360
  307. shouldAllow =
  308. (trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed);
  309. }
  310. // Call the call back with the permission.
  311. callback(shouldAllow);
  312. CFRelease(serverTrust);
  313. });
  314. return;
  315. }
  316. // Default handling for other Auth Challenges.
  317. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  318. }
  319. #pragma mark - Internal Methods
  320. /// Stores system completion handler with session ID as key.
  321. - (void)addSystemCompletionHandler:(FIRNetworkSystemCompletionHandler)handler
  322. forSession:(NSString *)identifier {
  323. if (!handler) {
  324. [_loggerDelegate
  325. firNetwork_logWithLevel:kFIRNetworkLogLevelError
  326. messageCode:kFIRNetworkMessageCodeURLSession009
  327. message:@"Cannot store nil system completion handler in network"];
  328. return;
  329. }
  330. if (!identifier.length) {
  331. [_loggerDelegate
  332. firNetwork_logWithLevel:kFIRNetworkLogLevelError
  333. messageCode:kFIRNetworkMessageCodeURLSession010
  334. message:@"Cannot store system completion handler with empty network "
  335. "session identifier"];
  336. return;
  337. }
  338. FIRMutableDictionary *systemCompletionHandlers =
  339. [[self class] sessionIDToSystemCompletionHandlerDictionary];
  340. if (systemCompletionHandlers[identifier]) {
  341. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
  342. messageCode:kFIRNetworkMessageCodeURLSession011
  343. message:@"Got multiple system handlers for a single session ID"
  344. context:identifier];
  345. }
  346. systemCompletionHandlers[identifier] = handler;
  347. }
  348. /// Calls the system provided completion handler with the session ID stored in the dictionary.
  349. /// The handler will be removed from the dictionary after being called.
  350. - (void)callSystemCompletionHandler:(NSString *)identifier {
  351. FIRMutableDictionary *systemCompletionHandlers =
  352. [[self class] sessionIDToSystemCompletionHandlerDictionary];
  353. FIRNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier];
  354. if (handler) {
  355. [systemCompletionHandlers removeObjectForKey:identifier];
  356. dispatch_async(dispatch_get_main_queue(), ^{
  357. handler();
  358. });
  359. }
  360. }
  361. /// Sets or updates the session ID of this session.
  362. - (void)setSessionID:(NSString *)sessionID {
  363. _sessionID = [sessionID copy];
  364. }
  365. /// Creates a background session configuration with the session ID using the supported method.
  366. - (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID {
  367. #if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \
  368. MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \
  369. (TARGET_OS_IOS && defined(__IPHONE_8_0) && \
  370. __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)
  371. // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name.
  372. return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
  373. #elif (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \
  374. MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \
  375. (TARGET_OS_IOS && defined(__IPHONE_8_0) && \
  376. __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0)
  377. // Do a runtime check to avoid a deprecation warning about using
  378. // +backgroundSessionConfiguration: on iOS 8.
  379. if ([NSURLSessionConfiguration
  380. respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) {
  381. // Running on iOS 8+/OS X 10.10+.
  382. return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
  383. } else {
  384. // Running on iOS 7/OS X 10.9.
  385. return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
  386. }
  387. #else
  388. // Building with an SDK earlier than iOS 8/OS X 10.10.
  389. return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
  390. #endif
  391. }
  392. - (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime {
  393. if (!folderURL.absoluteString.length) {
  394. return;
  395. }
  396. NSFileManager *fileManager = [NSFileManager defaultManager];
  397. NSError *error = nil;
  398. NSArray *properties = @[ NSURLCreationDateKey ];
  399. NSArray *directoryContent =
  400. [fileManager contentsOfDirectoryAtURL:folderURL
  401. includingPropertiesForKeys:properties
  402. options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
  403. error:&error];
  404. if (error && error.code != NSFileReadNoSuchFileError) {
  405. [_loggerDelegate
  406. firNetwork_logWithLevel:kFIRNetworkLogLevelDebug
  407. messageCode:kFIRNetworkMessageCodeURLSession012
  408. message:@"Cannot get files from the temporary network folder. Error"
  409. context:error];
  410. return;
  411. }
  412. if (!directoryContent.count) {
  413. return;
  414. }
  415. NSTimeInterval now = [NSDate date].timeIntervalSince1970;
  416. for (NSURL *tempFile in directoryContent) {
  417. NSDate *creationDate;
  418. BOOL getCreationDate =
  419. [tempFile getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL];
  420. if (!getCreationDate) {
  421. continue;
  422. }
  423. NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970;
  424. if (fabs(now - creationTimeInterval) > staleTime) {
  425. [self removeTempItemAtURL:tempFile];
  426. }
  427. }
  428. }
  429. /// Removes the temporary file written to disk for sending the request. It has to be cleaned up
  430. /// after the session is done.
  431. - (void)removeTempItemAtURL:(NSURL *)fileURL {
  432. if (!fileURL.absoluteString.length) {
  433. return;
  434. }
  435. NSFileManager *fileManager = [NSFileManager defaultManager];
  436. NSError *error = nil;
  437. if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) {
  438. [_loggerDelegate
  439. firNetwork_logWithLevel:kFIRNetworkLogLevelError
  440. messageCode:kFIRNetworkMessageCodeURLSession013
  441. message:@"Failed to remove temporary uploading data file. Error"
  442. context:error.localizedDescription];
  443. }
  444. }
  445. /// Gets the fetcher with the session ID.
  446. + (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier {
  447. NSMapTable *sessionIdentifierToFetcherMap = [self sessionIDToFetcherMap];
  448. FIRNetworkURLSession *session = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier];
  449. if (!session && [sessionIdentifier hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) {
  450. session = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil];
  451. [session setSessionID:sessionIdentifier];
  452. [sessionIdentifierToFetcherMap setObject:session forKey:sessionIdentifier];
  453. }
  454. return session;
  455. }
  456. /// Returns a map of the fetcher by session ID. Creates a map if it is not created.
  457. + (NSMapTable *)sessionIDToFetcherMap {
  458. static NSMapTable *sessionIDToFetcherMap;
  459. static dispatch_once_t sessionMapOnceToken;
  460. dispatch_once(&sessionMapOnceToken, ^{
  461. sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable];
  462. });
  463. return sessionIDToFetcherMap;
  464. }
  465. /// Returns a map of system provided completion handler by session ID. Creates a map if it is not
  466. /// created.
  467. + (FIRMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary {
  468. static FIRMutableDictionary *systemCompletionHandlers;
  469. static dispatch_once_t systemCompletionHandlerOnceToken;
  470. dispatch_once(&systemCompletionHandlerOnceToken, ^{
  471. systemCompletionHandlers = [[FIRMutableDictionary alloc] init];
  472. });
  473. return systemCompletionHandlers;
  474. }
  475. - (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID {
  476. NSString *tempName = [NSString stringWithFormat:@"FIRUpload_temp_%@", sessionID];
  477. return [_networkDirectoryURL URLByAppendingPathComponent:tempName];
  478. }
  479. /// Makes sure that the directory to store temp files exists. If not, tries to create it and returns
  480. /// YES. If there is anything wrong, returns NO.
  481. - (BOOL)ensureTemporaryDirectoryExists {
  482. NSFileManager *fileManager = [NSFileManager defaultManager];
  483. NSError *error = nil;
  484. // Create a temporary directory if it does not exist or was deleted.
  485. if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) {
  486. return YES;
  487. }
  488. if (error && error.code != NSFileReadNoSuchFileError) {
  489. [_loggerDelegate
  490. firNetwork_logWithLevel:kFIRNetworkLogLevelWarning
  491. messageCode:kFIRNetworkMessageCodeURLSession014
  492. message:@"Error while trying to access Network temp folder. Error"
  493. context:error];
  494. }
  495. NSError *writeError = nil;
  496. [fileManager createDirectoryAtURL:_networkDirectoryURL
  497. withIntermediateDirectories:YES
  498. attributes:nil
  499. error:&writeError];
  500. if (writeError) {
  501. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
  502. messageCode:kFIRNetworkMessageCodeURLSession015
  503. message:@"Cannot create temporary directory. Error"
  504. context:writeError];
  505. return NO;
  506. }
  507. // Set the iCloud exclusion attribute on the Documents URL.
  508. [self excludeFromBackupForURL:_networkDirectoryURL];
  509. return YES;
  510. }
  511. - (void)excludeFromBackupForURL:(NSURL *)url {
  512. if (!url.path) {
  513. return;
  514. }
  515. // Set the iCloud exclusion attribute on the Documents URL.
  516. NSError *preventBackupError = nil;
  517. [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError];
  518. if (preventBackupError) {
  519. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
  520. messageCode:kFIRNetworkMessageCodeURLSession016
  521. message:@"Cannot exclude temporary folder from iTunes backup"];
  522. }
  523. }
  524. - (void)URLSession:(NSURLSession *)session
  525. task:(NSURLSessionTask *)task
  526. willPerformHTTPRedirection:(NSHTTPURLResponse *)response
  527. newRequest:(NSURLRequest *)request
  528. completionHandler:(void (^)(NSURLRequest *))completionHandler {
  529. NSArray *nonAllowedRedirectionCodes = @[
  530. @(kFIRNetworkHTTPStatusCodeFound),
  531. @(kFIRNetworkHTTPStatusCodeMovedPermanently),
  532. @(kFIRNetworkHTTPStatusCodeMovedTemporarily),
  533. @(kFIRNetworkHTTPStatusCodeMultipleChoices)
  534. ];
  535. // Allow those not in the non allowed list to be followed.
  536. if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) {
  537. completionHandler(request);
  538. return;
  539. }
  540. // Do not allow redirection if the response code is in the non-allowed list.
  541. NSURLRequest *newRequest = request;
  542. if (response) {
  543. newRequest = nil;
  544. }
  545. completionHandler(newRequest);
  546. }
  547. #pragma mark - Helper Methods
  548. - (void)callCompletionHandler:(FIRNetworkURLSessionCompletionHandler)handler
  549. withResponse:(NSHTTPURLResponse *)response
  550. data:(NSData *)data
  551. error:(NSError *)error {
  552. if (error) {
  553. [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError
  554. messageCode:kFIRNetworkMessageCodeURLSession017
  555. message:@"Encounter network error. Code, error"
  556. contexts:@[@(error.code), error]];
  557. }
  558. if (handler) {
  559. dispatch_async(dispatch_get_main_queue(), ^{
  560. handler(response, data, _sessionID, error);
  561. });
  562. }
  563. }
  564. - (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig
  565. withRequest:(NSURLRequest *)request {
  566. sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields;
  567. sessionConfig.timeoutIntervalForRequest = request.timeoutInterval;
  568. sessionConfig.timeoutIntervalForResource = request.timeoutInterval;
  569. sessionConfig.requestCachePolicy = request.cachePolicy;
  570. }
  571. @end