1 // Copyright 2017 Google
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
15 #import <Foundation/Foundation.h>
17 #import "Private/GULNetworkURLSession.h"
19 #import <GoogleUtilities/GULLogger.h>
20 #import "Private/GULMutableDictionary.h"
21 #import "Private/GULNetworkConstants.h"
22 #import "Private/GULNetworkMessageCode.h"
24 @implementation GULNetworkURLSession {
25 /// The handler to be called when the request completes or error has occurs.
26 GULNetworkURLSessionCompletionHandler _completionHandler;
28 /// Session ID generated randomly with a fixed prefix.
31 #pragma clang diagnostic push
32 #pragma clang diagnostic ignored "-Wunguarded-availability"
33 /// The session configuration. NSURLSessionConfiguration' is only available on iOS 7.0 or newer.
34 NSURLSessionConfiguration *_sessionConfig;
37 /// The path to the directory where all temporary files are stored before uploading.
38 NSURL *_networkDirectoryURL;
40 /// The downloaded data from fetching.
41 NSData *_downloadedData;
43 /// The path to the temporary file which stores the uploading data.
44 NSURL *_uploadingFileURL;
46 /// The current request.
47 NSURLRequest *_request;
52 - (instancetype)initWithNetworkLoggerDelegate:(id<GULNetworkLoggerDelegate>)networkLoggerDelegate {
55 // Create URL to the directory where all temporary files to upload have to be stored.
57 NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
58 NSString *applicationSupportDirectory = paths.firstObject;
59 NSArray *tempPathComponents = @[
60 applicationSupportDirectory, kGULNetworkApplicationSupportSubdirectory,
61 kGULNetworkTempDirectoryName
63 _networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents];
64 _sessionID = [NSString stringWithFormat:@"%@-%@", kGULNetworkBackgroundSessionConfigIDPrefix,
65 [[NSUUID UUID] UUIDString]];
66 _loggerDelegate = networkLoggerDelegate;
71 #pragma mark - External Methods
73 #pragma mark - To be called from AppDelegate
75 + (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID
77 (GULNetworkSystemCompletionHandler)systemCompletionHandler {
78 // The session may not be Analytics background. Ignore those that do not have the prefix.
79 if (![sessionID hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) {
82 GULNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID];
84 [fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID];
86 GULLogError(kGULLoggerNetwork, NO,
87 [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork003],
88 @"Failed to retrieve background session with ID %@ after app is relaunched.",
93 #pragma mark - External Methods
95 /// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the
97 - (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
98 completionHandler:(GULNetworkURLSessionCompletionHandler)handler
99 API_AVAILABLE(ios(7.0)) {
100 // NSURLSessionUploadTask does not work with NSData in the background.
101 // To avoid this issue, write the data to a temporary file to upload it.
102 // Make a temporary file with the data subset.
103 _uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID];
105 NSURLSessionUploadTask *postRequestTask;
106 NSURLSession *session;
107 BOOL didWriteFile = NO;
109 // Clean up the entire temp folder to avoid temp files that remain in case the previous session
110 // crashed and did not clean up.
111 [self maybeRemoveTempFilesAtURL:_networkDirectoryURL
112 expiringTime:kGULNetworkTempFolderExpireTime];
114 // If there is no background network enabled, no need to write to file. This will allow default
115 // network session which runs on the foreground.
116 if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) {
117 didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path
118 options:NSDataWritingAtomic
122 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
123 messageCode:kGULNetworkMessageCodeURLSession000
124 message:@"Failed to write request data to file"
130 // Exclude this file from backing up to iTunes. There are conflicting reports that excluding
131 // directory from backing up does not excluding files of that directory from backing up.
132 [self excludeFromBackupForURL:_uploadingFileURL];
134 _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
135 [self populateSessionConfig:_sessionConfig withRequest:request];
136 session = [NSURLSession sessionWithConfiguration:_sessionConfig
138 delegateQueue:[NSOperationQueue mainQueue]];
139 postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL];
141 // If we cannot write to file, just send it in the foreground.
142 _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
143 [self populateSessionConfig:_sessionConfig withRequest:request];
144 _sessionConfig.URLCache = nil;
145 session = [NSURLSession sessionWithConfiguration:_sessionConfig
147 delegateQueue:[NSOperationQueue mainQueue]];
148 postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody];
151 if (!session || !postRequestTask) {
152 NSError *error = [[NSError alloc]
153 initWithDomain:kGULNetworkErrorDomain
154 code:GULErrorCodeNetworkRequestCreation
155 userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}];
156 [self callCompletionHandler:handler withResponse:nil data:nil error:error];
160 // Save the session into memory.
161 NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
162 [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
164 _request = [request copy];
166 // Store completion handler because background session does not accept handler block but custom
168 _completionHandler = [handler copy];
169 [postRequestTask resume];
174 /// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session.
175 - (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
176 completionHandler:(GULNetworkURLSessionCompletionHandler)handler
177 API_AVAILABLE(ios(7.0)) {
178 if (_backgroundNetworkEnabled) {
179 _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
181 _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
184 [self populateSessionConfig:_sessionConfig withRequest:request];
186 // Do not cache the GET request.
187 _sessionConfig.URLCache = nil;
189 NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig
191 delegateQueue:[NSOperationQueue mainQueue]];
192 NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
194 if (!session || !downloadTask) {
195 NSError *error = [[NSError alloc]
196 initWithDomain:kGULNetworkErrorDomain
197 code:GULErrorCodeNetworkRequestCreation
198 userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}];
199 [self callCompletionHandler:handler withResponse:nil data:nil error:error];
203 // Save the session into memory.
204 NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap];
205 [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID];
207 _request = [request copy];
209 _completionHandler = [handler copy];
210 [downloadTask resume];
215 #pragma mark - NSURLSessionTaskDelegate
217 /// Called by the NSURLSession once the download task is completed. The file is saved in the
218 /// provided URL so we need to read the data and store into _downloadedData. Once the session is
219 /// completed, URLSession:task:didCompleteWithError will be called and the completion handler will
220 /// be called with the downloaded data.
221 - (void)URLSession:(NSURLSession *)session
222 downloadTask:(NSURLSessionDownloadTask *)task
223 didFinishDownloadingToURL:(NSURL *)url API_AVAILABLE(ios(7.0)) {
226 GULNetwork_logWithLevel:kGULNetworkLogLevelError
227 messageCode:kGULNetworkMessageCodeURLSession001
228 message:@"Unable to read downloaded data from empty temp path"];
229 _downloadedData = nil;
234 _downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error];
237 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
238 messageCode:kGULNetworkMessageCodeURLSession002
239 message:@"Cannot read the content of downloaded data"
241 _downloadedData = nil;
245 #if TARGET_OS_IOS || TARGET_OS_TV
246 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
247 API_AVAILABLE(ios(7.0)) {
248 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
249 messageCode:kGULNetworkMessageCodeURLSession003
250 message:@"Background session finished"
251 context:session.configuration.identifier];
252 [self callSystemCompletionHandler:session.configuration.identifier];
256 - (void)URLSession:(NSURLSession *)session
257 task:(NSURLSessionTask *)task
258 didCompleteWithError:(NSError *)error API_AVAILABLE(ios(7.0)) {
259 // Avoid any chance of recursive behavior leading to it being used repeatedly.
260 GULNetworkURLSessionCompletionHandler handler = _completionHandler;
261 _completionHandler = nil;
264 // The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7.
265 NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP");
267 // The server responded so ignore the error created by the system.
270 error = [[NSError alloc]
271 initWithDomain:kGULNetworkErrorDomain
272 code:GULErrorCodeNetworkInvalidResponse
273 userInfo:@{kGULNetworkErrorContext : @"Network Error: Empty network response"}];
276 [self callCompletionHandler:handler
277 withResponse:(NSHTTPURLResponse *)task.response
281 // Remove the temp file to avoid trashing devices with lots of temp files.
282 [self removeTempItemAtURL:_uploadingFileURL];
284 // Try to clean up stale files again.
285 [self maybeRemoveTempFilesAtURL:_networkDirectoryURL
286 expiringTime:kGULNetworkTempFolderExpireTime];
289 - (void)URLSession:(NSURLSession *)session
290 task:(NSURLSessionTask *)task
291 didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
292 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
293 NSURLCredential *credential))completionHandler
294 API_AVAILABLE(ios(7.0)) {
295 // The handling is modeled after GTMSessionFetcher.
296 if ([challenge.protectionSpace.authenticationMethod
297 isEqualToString:NSURLAuthenticationMethodServerTrust]) {
298 SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
299 if (serverTrust == NULL) {
300 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
301 messageCode:kGULNetworkMessageCodeURLSession004
302 message:@"Received empty server trust for host. Host"
303 context:_request.URL];
304 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
307 NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
309 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning
310 messageCode:kGULNetworkMessageCodeURLSession005
311 message:@"Unable to verify server identity. Host"
312 context:_request.URL];
313 completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
317 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
318 messageCode:kGULNetworkMessageCodeURLSession006
319 message:@"Received SSL challenge for host. Host"
320 context:_request.URL];
322 void (^callback)(BOOL) = ^(BOOL allow) {
324 completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
326 [self->_loggerDelegate
327 GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
328 messageCode:kGULNetworkMessageCodeURLSession007
329 message:@"Cancelling authentication challenge for host. Host"
330 context:self->_request.URL];
331 completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
335 // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7.
336 CFRetain(serverTrust);
338 // Evaluate the certificate chain.
340 // The delegate queue may be the main thread. Trust evaluation could cause some
341 // blocking network activity, so we must evaluate async, as documented at
342 // https://developer.apple.com/library/ios/technotes/tn2232/
343 dispatch_queue_t evaluateBackgroundQueue =
344 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
346 dispatch_async(evaluateBackgroundQueue, ^{
347 SecTrustResultType trustEval = kSecTrustResultInvalid;
351 @synchronized([GULNetworkURLSession class]) {
352 trustError = SecTrustEvaluate(serverTrust, &trustEval);
355 if (trustError != errSecSuccess) {
356 [self->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
357 messageCode:kGULNetworkMessageCodeURLSession008
358 message:@"Cannot evaluate server trust. Error, host"
359 contexts:@[ @(trustError), self->_request.URL ]];
362 // Having a trust level "unspecified" by the user is the usual result, described at
363 // https://developer.apple.com/library/mac/qa/qa1360
365 (trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed);
368 // Call the call back with the permission.
369 callback(shouldAllow);
371 CFRelease(serverTrust);
376 // Default handling for other Auth Challenges.
377 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
380 #pragma mark - Internal Methods
382 /// Stores system completion handler with session ID as key.
383 - (void)addSystemCompletionHandler:(GULNetworkSystemCompletionHandler)handler
384 forSession:(NSString *)identifier {
387 GULNetwork_logWithLevel:kGULNetworkLogLevelError
388 messageCode:kGULNetworkMessageCodeURLSession009
389 message:@"Cannot store nil system completion handler in network"];
393 if (!identifier.length) {
395 GULNetwork_logWithLevel:kGULNetworkLogLevelError
396 messageCode:kGULNetworkMessageCodeURLSession010
398 @"Cannot store system completion handler with empty network "
399 "session identifier"];
403 GULMutableDictionary *systemCompletionHandlers =
404 [[self class] sessionIDToSystemCompletionHandlerDictionary];
405 if (systemCompletionHandlers[identifier]) {
406 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning
407 messageCode:kGULNetworkMessageCodeURLSession011
408 message:@"Got multiple system handlers for a single session ID"
412 systemCompletionHandlers[identifier] = handler;
415 /// Calls the system provided completion handler with the session ID stored in the dictionary.
416 /// The handler will be removed from the dictionary after being called.
417 - (void)callSystemCompletionHandler:(NSString *)identifier {
418 GULMutableDictionary *systemCompletionHandlers =
419 [[self class] sessionIDToSystemCompletionHandlerDictionary];
420 GULNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier];
423 [systemCompletionHandlers removeObjectForKey:identifier];
425 dispatch_async(dispatch_get_main_queue(), ^{
431 /// Sets or updates the session ID of this session.
432 - (void)setSessionID:(NSString *)sessionID {
433 _sessionID = [sessionID copy];
436 /// Creates a background session configuration with the session ID using the supported method.
437 - (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID
438 API_AVAILABLE(ios(7.0)) {
439 #if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \
440 MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \
442 (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0)
444 // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name.
445 return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
447 #elif (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \
448 MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \
449 (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0)
451 // Do a runtime check to avoid a deprecation warning about using
452 // +backgroundSessionConfiguration: on iOS 8.
453 if ([NSURLSessionConfiguration
454 respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) {
455 // Running on iOS 8+/OS X 10.10+.
456 #pragma clang diagnostic push
457 #pragma clang diagnostic ignored "-Wunguarded-availability"
458 return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
459 #pragma clang diagnostic pop
461 // Running on iOS 7/OS X 10.9.
462 return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
466 // Building with an SDK earlier than iOS 8/OS X 10.10.
467 return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID];
471 - (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime {
472 if (!folderURL.absoluteString.length) {
476 NSFileManager *fileManager = [NSFileManager defaultManager];
477 NSError *error = nil;
479 NSArray *properties = @[ NSURLCreationDateKey ];
480 NSArray *directoryContent =
481 [fileManager contentsOfDirectoryAtURL:folderURL
482 includingPropertiesForKeys:properties
483 options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
485 if (error && error.code != NSFileReadNoSuchFileError) {
487 GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
488 messageCode:kGULNetworkMessageCodeURLSession012
489 message:@"Cannot get files from the temporary network folder. Error"
494 if (!directoryContent.count) {
498 NSTimeInterval now = [NSDate date].timeIntervalSince1970;
499 for (NSURL *tempFile in directoryContent) {
500 NSDate *creationDate;
501 BOOL getCreationDate =
502 [tempFile getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL];
503 if (!getCreationDate) {
506 NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970;
507 if (fabs(now - creationTimeInterval) > staleTime) {
508 [self removeTempItemAtURL:tempFile];
513 /// Removes the temporary file written to disk for sending the request. It has to be cleaned up
514 /// after the session is done.
515 - (void)removeTempItemAtURL:(NSURL *)fileURL {
516 if (!fileURL.absoluteString.length) {
520 NSFileManager *fileManager = [NSFileManager defaultManager];
521 NSError *error = nil;
523 if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) {
525 GULNetwork_logWithLevel:kGULNetworkLogLevelError
526 messageCode:kGULNetworkMessageCodeURLSession013
527 message:@"Failed to remove temporary uploading data file. Error"
528 context:error.localizedDescription];
532 /// Gets the fetcher with the session ID.
533 + (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier {
534 NSMapTable *sessionIdentifierToFetcherMap = [self sessionIDToFetcherMap];
535 GULNetworkURLSession *session = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier];
536 if (!session && [sessionIdentifier hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) {
537 session = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil];
538 [session setSessionID:sessionIdentifier];
539 [sessionIdentifierToFetcherMap setObject:session forKey:sessionIdentifier];
544 /// Returns a map of the fetcher by session ID. Creates a map if it is not created.
545 + (NSMapTable *)sessionIDToFetcherMap {
546 static NSMapTable *sessionIDToFetcherMap;
548 static dispatch_once_t sessionMapOnceToken;
549 dispatch_once(&sessionMapOnceToken, ^{
550 sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable];
552 return sessionIDToFetcherMap;
555 /// Returns a map of system provided completion handler by session ID. Creates a map if it is not
557 + (GULMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary {
558 static GULMutableDictionary *systemCompletionHandlers;
560 static dispatch_once_t systemCompletionHandlerOnceToken;
561 dispatch_once(&systemCompletionHandlerOnceToken, ^{
562 systemCompletionHandlers = [[GULMutableDictionary alloc] init];
564 return systemCompletionHandlers;
567 - (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID {
568 NSString *tempName = [NSString stringWithFormat:@"GULUpload_temp_%@", sessionID];
569 return [_networkDirectoryURL URLByAppendingPathComponent:tempName];
572 /// Makes sure that the directory to store temp files exists. If not, tries to create it and returns
573 /// YES. If there is anything wrong, returns NO.
574 - (BOOL)ensureTemporaryDirectoryExists {
575 NSFileManager *fileManager = [NSFileManager defaultManager];
576 NSError *error = nil;
578 // Create a temporary directory if it does not exist or was deleted.
579 if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) {
583 if (error && error.code != NSFileReadNoSuchFileError) {
585 GULNetwork_logWithLevel:kGULNetworkLogLevelWarning
586 messageCode:kGULNetworkMessageCodeURLSession014
587 message:@"Error while trying to access Network temp folder. Error"
591 NSError *writeError = nil;
593 [fileManager createDirectoryAtURL:_networkDirectoryURL
594 withIntermediateDirectories:YES
598 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
599 messageCode:kGULNetworkMessageCodeURLSession015
600 message:@"Cannot create temporary directory. Error"
605 // Set the iCloud exclusion attribute on the Documents URL.
606 [self excludeFromBackupForURL:_networkDirectoryURL];
611 - (void)excludeFromBackupForURL:(NSURL *)url {
616 // Set the iCloud exclusion attribute on the Documents URL.
617 NSError *preventBackupError = nil;
618 [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError];
619 if (preventBackupError) {
620 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
621 messageCode:kGULNetworkMessageCodeURLSession016
622 message:@"Cannot exclude temporary folder from iTunes backup"];
626 - (void)URLSession:(NSURLSession *)session
627 task:(NSURLSessionTask *)task
628 willPerformHTTPRedirection:(NSHTTPURLResponse *)response
629 newRequest:(NSURLRequest *)request
630 completionHandler:(void (^)(NSURLRequest *))completionHandler API_AVAILABLE(ios(7.0)) {
631 NSArray *nonAllowedRedirectionCodes = @[
632 @(kGULNetworkHTTPStatusCodeFound), @(kGULNetworkHTTPStatusCodeMovedPermanently),
633 @(kGULNetworkHTTPStatusCodeMovedTemporarily), @(kGULNetworkHTTPStatusCodeMultipleChoices)
636 // Allow those not in the non allowed list to be followed.
637 if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) {
638 completionHandler(request);
642 // Do not allow redirection if the response code is in the non-allowed list.
643 NSURLRequest *newRequest = request;
649 completionHandler(newRequest);
652 #pragma mark - Helper Methods
654 - (void)callCompletionHandler:(GULNetworkURLSessionCompletionHandler)handler
655 withResponse:(NSHTTPURLResponse *)response
657 error:(NSError *)error {
659 [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
660 messageCode:kGULNetworkMessageCodeURLSession017
661 message:@"Encounter network error. Code, error"
662 contexts:@[ @(error.code), error ]];
666 dispatch_async(dispatch_get_main_queue(), ^{
667 handler(response, data, self->_sessionID, error);
672 - (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig
673 withRequest:(NSURLRequest *)request API_AVAILABLE(ios(7.0)) {
674 sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields;
675 sessionConfig.timeoutIntervalForRequest = request.timeoutInterval;
676 sessionConfig.timeoutIntervalForResource = request.timeoutInterval;
677 sessionConfig.requestCachePolicy = request.cachePolicy;