From 98b6eef71eac368692ae249f056a75d89ed0350d Mon Sep 17 00:00:00 2001 From: Paul Beusterien Date: Fri, 6 Jul 2018 09:37:23 -0700 Subject: Split GoogleUtilities from FirebaseCore (#1370) --- .travis.yml | 38 +- CMakeLists.txt | 1 + Example/Core/Tests/FIRAppEnvironmentUtilTest.m | 12 +- Example/Core/Tests/FIRLoggerTest.m | 39 +- Example/Core/Tests/FIRMutableDictionaryTest.m | 6 +- Example/Core/Tests/FIRNetworkTest.m | 57 +- Example/Core/Tests/FIRReachabilityCheckerTest.m | 76 ++- Example/Firebase.xcodeproj/project.pbxproj | 40 +- Example/Messaging/Tests/FIRMessagingClientTest.m | 8 +- .../Messaging/Tests/FIRMessagingConnectionTest.m | 3 +- Example/Messaging/Tests/FIRMessagingFakeSocket.m | 3 +- Example/Podfile | 3 +- Firebase/Auth/Source/FIRAuth.m | 4 +- Firebase/Auth/Source/FIRAuthAPNSTokenManager.m | 44 +- Firebase/Auth/Source/FIRAuthAppDelegateProxy.m | 4 +- Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m | 4 +- Firebase/Core/FIRApp.m | 1 - Firebase/Core/FIRLogger.m | 175 +----- Firebase/Core/FIRMutableDictionary.m | 97 --- Firebase/Core/FIRNetwork.m | 390 ------------ Firebase/Core/FIRNetworkConstants.m | 39 -- Firebase/Core/FIRNetworkURLSession.m | 669 --------------------- Firebase/Core/FIRReachabilityChecker.m | 256 -------- Firebase/Core/FIRVersion.m | 4 +- Firebase/Core/Private/FIRMutableDictionary.h | 46 -- Firebase/Core/Private/FIRNetwork.h | 87 --- Firebase/Core/Private/FIRNetworkConstants.h | 75 --- Firebase/Core/Private/FIRNetworkLoggerProtocol.h | 50 -- Firebase/Core/Private/FIRNetworkMessageCode.h | 52 -- Firebase/Core/Private/FIRNetworkURLSession.h | 60 -- .../Core/Private/FIRReachabilityChecker+Internal.h | 47 -- Firebase/Core/Private/FIRReachabilityChecker.h | 83 --- Firebase/Core/Private/FIRVersion.h | 4 +- Firebase/Core/Public/FIRLoggerLevel.h | 3 + Firebase/Core/third_party/FIRAppEnvironmentUtil.h | 43 -- Firebase/Core/third_party/FIRAppEnvironmentUtil.m | 239 -------- Firebase/Messaging/FIRMessaging.m | 23 +- Firebase/Messaging/FIRMessagingClient.h | 4 +- Firebase/Messaging/FIRMessagingClient.m | 10 +- .../third_party/GULAppEnvironmentUtil.h | 43 ++ .../third_party/GULAppEnvironmentUtil.m | 236 ++++++++ Firebase/Utilities/Logger/GULLogger.m | 214 +++++++ Firebase/Utilities/Logger/Private/GULLogger.h | 151 +++++ Firebase/Utilities/Logger/Public/GULLoggerLevel.h | 35 ++ Firebase/Utilities/NSData+zlib/GULNSData+zlib.h | 49 ++ Firebase/Utilities/NSData+zlib/GULNSData+zlib.m | 207 +++++++ Firebase/Utilities/Network/GULMutableDictionary.m | 97 +++ Firebase/Utilities/Network/GULNetwork.m | 388 ++++++++++++ Firebase/Utilities/Network/GULNetworkConstants.m | 40 ++ Firebase/Utilities/Network/GULNetworkURLSession.m | 669 +++++++++++++++++++++ .../Network/Private/GULMutableDictionary.h | 46 ++ Firebase/Utilities/Network/Private/GULNetwork.h | 87 +++ .../Network/Private/GULNetworkConstants.h | 79 +++ .../Network/Private/GULNetworkLoggerProtocol.h | 51 ++ .../Network/Private/GULNetworkMessageCode.h | 44 ++ .../Network/Private/GULNetworkURLSession.h | 60 ++ .../Reachability/GULReachabilityChecker+Internal.h | 47 ++ .../Reachability/GULReachabilityChecker.m | 240 ++++++++ .../Reachability/Private/GULReachabilityChecker.h | 77 +++ .../Private/GULReachabilityMessageCode.h | 27 + FirebaseAuth.podspec | 1 + FirebaseCore.podspec | 11 +- FirebaseMessaging.podspec | 2 +- Firestore/CMakeLists.txt | 1 + .../Example/Firestore.xcodeproj/project.pbxproj | 6 - Firestore/Example/Podfile | 3 +- .../src/firebase/firestore/auth/CMakeLists.txt | 1 + .../src/firebase/firestore/util/CMakeLists.txt | 1 + .../firebase/firestore/testutil/CMakeLists.txt | 1 + Functions/Example/Podfile | 1 + GoogleUtilities.podspec | 71 +++ cmake/FindGoogleUtilities.cmake | 56 ++ cmake/external/GoogleUtilities.cmake | 23 + 73 files changed, 3294 insertions(+), 2570 deletions(-) delete mode 100644 Firebase/Core/FIRMutableDictionary.m delete mode 100644 Firebase/Core/FIRNetwork.m delete mode 100644 Firebase/Core/FIRNetworkConstants.m delete mode 100644 Firebase/Core/FIRNetworkURLSession.m delete mode 100644 Firebase/Core/FIRReachabilityChecker.m delete mode 100644 Firebase/Core/Private/FIRMutableDictionary.h delete mode 100644 Firebase/Core/Private/FIRNetwork.h delete mode 100644 Firebase/Core/Private/FIRNetworkConstants.h delete mode 100644 Firebase/Core/Private/FIRNetworkLoggerProtocol.h delete mode 100644 Firebase/Core/Private/FIRNetworkMessageCode.h delete mode 100644 Firebase/Core/Private/FIRNetworkURLSession.h delete mode 100644 Firebase/Core/Private/FIRReachabilityChecker+Internal.h delete mode 100644 Firebase/Core/Private/FIRReachabilityChecker.h delete mode 100644 Firebase/Core/third_party/FIRAppEnvironmentUtil.h delete mode 100644 Firebase/Core/third_party/FIRAppEnvironmentUtil.m create mode 100644 Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.h create mode 100644 Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.m create mode 100644 Firebase/Utilities/Logger/GULLogger.m create mode 100644 Firebase/Utilities/Logger/Private/GULLogger.h create mode 100644 Firebase/Utilities/Logger/Public/GULLoggerLevel.h create mode 100644 Firebase/Utilities/NSData+zlib/GULNSData+zlib.h create mode 100644 Firebase/Utilities/NSData+zlib/GULNSData+zlib.m create mode 100644 Firebase/Utilities/Network/GULMutableDictionary.m create mode 100644 Firebase/Utilities/Network/GULNetwork.m create mode 100644 Firebase/Utilities/Network/GULNetworkConstants.m create mode 100644 Firebase/Utilities/Network/GULNetworkURLSession.m create mode 100644 Firebase/Utilities/Network/Private/GULMutableDictionary.h create mode 100644 Firebase/Utilities/Network/Private/GULNetwork.h create mode 100644 Firebase/Utilities/Network/Private/GULNetworkConstants.h create mode 100644 Firebase/Utilities/Network/Private/GULNetworkLoggerProtocol.h create mode 100644 Firebase/Utilities/Network/Private/GULNetworkMessageCode.h create mode 100644 Firebase/Utilities/Network/Private/GULNetworkURLSession.h create mode 100644 Firebase/Utilities/Reachability/GULReachabilityChecker+Internal.h create mode 100644 Firebase/Utilities/Reachability/GULReachabilityChecker.m create mode 100644 Firebase/Utilities/Reachability/Private/GULReachabilityChecker.h create mode 100644 Firebase/Utilities/Reachability/Private/GULReachabilityMessageCode.h create mode 100644 GoogleUtilities.podspec create mode 100644 cmake/FindGoogleUtilities.cmake create mode 100644 cmake/external/GoogleUtilities.cmake diff --git a/.travis.yml b/.travis.yml index dea83ed..79903c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -52,50 +52,60 @@ jobs: - stage: test env: - PROJECT=Firebase PLATFORM=iOS METHOD=pod-lib-lint + # Set ALT_SOURCES like the following to continue lint testing until release when Utilities + # or Core APIs change. GoogleUtilities.podspec and FirebaseCore.podspec should be + # manually pushed to a temporary Specs repo. See + # https://guides.cocoapods.org/making/private-cocoapods. + # ALT_SOURCES="--sources=git@github.com:paulb777/Specs.git,https://github.com/CocoaPods/Specs.git" + - ALT_SOURCES="--sources=https://github.com/paulb777/Specs.git,https://github.com/CocoaPods/Specs.git" before_install: - ./scripts/if_changed.sh ./scripts/install_prereqs.sh script: - - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseCore.podspec - - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseAuth.podspec - - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseDatabase.podspec - - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseMessaging.podspec - - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseStorage.podspec - - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseFunctions.podspec + - ./scripts/if_changed.sh bundle exec pod lib lint GoogleUtilities.podspec + - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseCore.podspec $ALT_SOURCES + - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseAuth.podspec $ALT_SOURCES + - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseDatabase.podspec $ALT_SOURCES + - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseMessaging.podspec $ALT_SOURCES + - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseStorage.podspec $ALT_SOURCES + - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseFunctions.podspec $ALT_SOURCES - stage: test env: - PROJECT=Firestore PLATFORM=iOS METHOD=pod-lib-lint + - ALT_SOURCES="--sources=https://github.com/paulb777/Specs.git,https://github.com/CocoaPods/Specs.git" before_install: - ./scripts/if_changed.sh ./scripts/install_prereqs.sh script: # Eliminate the one warning from BoringSSL when CocoaPods 1.6.0 is available. # The travis_wait is necessary because the command takes more than 10 minutes. - - travis_wait ./scripts/if_changed.sh bundle exec pod lib lint FirebaseFirestore.podspec --allow-warnings --no-subspecs + - travis_wait ./scripts/if_changed.sh bundle exec pod lib lint FirebaseFirestore.podspec --allow-warnings --no-subspecs $ALT_SOURCES # pod lib lint to check build and warnings for static library build - only on cron jobs - stage: test env: - PROJECT=Firebase PLATFORM=iOS METHOD=pod-lib-lint + - ALT_SOURCES="--sources=https://github.com/paulb777/Specs.git,https://github.com/CocoaPods/Specs.git" before_install: - ./scripts/if_cron.sh ./scripts/install_prereqs.sh script: - - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseCore.podspec --use-libraries - - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseAuth.podspec --use-libraries - - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseDatabase.podspec --use-libraries + - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseCore.podspec --use-libraries $ALT_SOURCES + - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseAuth.podspec --use-libraries $ALT_SOURCES + - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseDatabase.podspec --use-libraries $ALT_SOURCES # The Protobuf dependency of FirebaseMessaging has warnings with --use-libraries - - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseMessaging.podspec --use-libraries --allow-warnings - - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseStorage.podspec --use-libraries - - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseFunctions.podspec --use-libraries + - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseMessaging.podspec --use-libraries --allow-warnings $ALT_SOURCES + - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseStorage.podspec --use-libraries $ALT_SOURCES + - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseFunctions.podspec --use-libraries $ALT_SOURCES - stage: test env: - PROJECT=Firestore PLATFORM=iOS METHOD=pod-lib-lint + - ALT_SOURCES="--sources=https://github.com/paulb777/Specs.git,https://github.com/CocoaPods/Specs.git" before_install: - ./scripts/if_cron.sh ./scripts/install_prereqs.sh script: # TBD - non-portable path warnings # The travis_wait is necessary because the command takes more than 10 minutes. - - travis_wait ./scripts/if_cron.sh bundle exec pod lib lint FirebaseFirestore.podspec --use-libraries --allow-warnings --no-subspecs + - travis_wait ./scripts/if_cron.sh bundle exec pod lib lint FirebaseFirestore.podspec --use-libraries --allow-warnings --no-subspecs $ALT_SOURCES # Alternative platforms diff --git a/CMakeLists.txt b/CMakeLists.txt index a935c4d..10d8ce2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ enable_testing() # These are ordered by a topological sort on DEPENDS attributes. This is # required because CMake fails the build if you have a DEPENDS on a target that # does not exist yet. +include(external/GoogleUtilities) include(external/FirebaseCore) include(external/googletest) include(external/zlib) diff --git a/Example/Core/Tests/FIRAppEnvironmentUtilTest.m b/Example/Core/Tests/FIRAppEnvironmentUtilTest.m index 2469e8d..4ac2ef5 100644 --- a/Example/Core/Tests/FIRAppEnvironmentUtilTest.m +++ b/Example/Core/Tests/FIRAppEnvironmentUtilTest.m @@ -15,17 +15,17 @@ #import #import -#import +#import #import "FIRTestCase.h" -@interface FIRAppEnvironmentUtilTest : FIRTestCase +@interface GULAppEnvironmentUtilTest : FIRTestCase @property(nonatomic) id processInfoMock; @end -@implementation FIRAppEnvironmentUtilTest +@implementation GULAppEnvironmentUtilTest - (void)setUp { [super setUp]; @@ -43,21 +43,21 @@ NSOperatingSystemVersion osTen = {.majorVersion = 10, .minorVersion = 0, .patchVersion = 0}; OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTen); - XCTAssertTrue([[FIRAppEnvironmentUtil systemVersion] isEqualToString:@"10.0"]); + XCTAssertTrue([[GULAppEnvironmentUtil systemVersion] isEqualToString:@"10.0"]); } - (void)testSystemVersionInfoMajorMinor { NSOperatingSystemVersion osTenTwo = {.majorVersion = 10, .minorVersion = 2, .patchVersion = 0}; OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTenTwo); - XCTAssertTrue([[FIRAppEnvironmentUtil systemVersion] isEqualToString:@"10.2"]); + XCTAssertTrue([[GULAppEnvironmentUtil systemVersion] isEqualToString:@"10.2"]); } - (void)testSystemVersionInfoMajorMinorPatch { NSOperatingSystemVersion osTenTwoOne = {.majorVersion = 10, .minorVersion = 2, .patchVersion = 1}; OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTenTwoOne); - XCTAssertTrue([[FIRAppEnvironmentUtil systemVersion] isEqualToString:@"10.2.1"]); + XCTAssertTrue([[GULAppEnvironmentUtil systemVersion] isEqualToString:@"10.2.1"]); } @end diff --git a/Example/Core/Tests/FIRLoggerTest.m b/Example/Core/Tests/FIRLoggerTest.m index b871244..f179bde 100644 --- a/Example/Core/Tests/FIRLoggerTest.m +++ b/Example/Core/Tests/FIRLoggerTest.m @@ -17,7 +17,10 @@ #import "FIRTestCase.h" +// TODO - FIRLoggerTest should be split into a separate FIRLoggerTest and GULLoggerTest. +// No test should include both includes. #import +#import #import @@ -25,19 +28,19 @@ extern NSString *const kFIRDisableDebugModeApplicationArgument; extern NSString *const kFIREnableDebugModeApplicationArgument; -extern NSString *const kFIRPersistedDebugModeKey; +extern NSString *const kGULPersistedDebugModeKey; -extern const char *kFIRLoggerASLClientFacilityName; +extern const char *kGULLoggerASLClientFacilityName; extern void FIRResetLogger(void); extern void FIRSetLoggerUserDefaults(NSUserDefaults *defaults); -extern aslclient getFIRLoggerClient(void); +extern aslclient getGULLoggerClient(void); -extern dispatch_queue_t getFIRClientQueue(void); +extern dispatch_queue_t getGULClientQueue(void); -extern BOOL getFIRLoggerDebugMode(void); +extern BOOL getGULLoggerDebugMode(void); static NSString *const kMessageCode = @"I-COR000001"; @@ -95,9 +98,11 @@ static NSString *const kMessageCode = @"I-COR000001"; FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error."); // Assert. - NSNumber *debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey]; +#if MAKE_THREAD_SAFE + NSNumber *debugMode = [self.defaults objectForKey:kGULPersistedDebugModeKey]; XCTAssertNil(debugMode); - XCTAssertFalse(getFIRLoggerDebugMode()); + XCTAssertFalse(getGULLoggerDebugMode()); +#endif // Stop. [processInfoMock stopMocking]; @@ -112,10 +117,12 @@ static NSString *const kMessageCode = @"I-COR000001"; // Test. FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error."); +#ifdef MAKE_THREAD_SAFE // Assert. - NSNumber *debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey]; + NSNumber *debugMode = [self.defaults objectForKey:kGULPersistedDebugModeKey]; XCTAssertTrue(debugMode.boolValue); - XCTAssertTrue(getFIRLoggerDebugMode()); + XCTAssertTrue(getGULLoggerDebugMode()); +#endif // Stop. [processInfoMock stopMocking]; @@ -124,15 +131,17 @@ static NSString *const kMessageCode = @"I-COR000001"; - (void)testInitializeASLForDebugModeWithUserDefaults { // Stub. NSNumber *debugMode = @YES; - [self.defaults setBool:debugMode.boolValue forKey:kFIRPersistedDebugModeKey]; + [self.defaults setBool:debugMode.boolValue forKey:kGULPersistedDebugModeKey]; // Test. FIRLogError(kFIRLoggerCore, kMessageCode, @"Some error."); // Assert. - debugMode = [self.defaults objectForKey:kFIRPersistedDebugModeKey]; + debugMode = [self.defaults objectForKey:kGULPersistedDebugModeKey]; XCTAssertTrue(debugMode.boolValue); - XCTAssertTrue(getFIRLoggerDebugMode()); +#if MAKE_THREAD_SAFE + XCTAssertTrue(getGULLoggerDebugMode()); +#endif } - (void)testMessageCodeFormat { @@ -240,7 +249,7 @@ static NSString *const kMessageCode = @"I-COR000001"; - (void)drainFIRClientQueue { dispatch_semaphore_t workerSemaphore = dispatch_semaphore_create(0); - dispatch_async(getFIRClientQueue(), ^{ + dispatch_async(getGULClientQueue(), ^{ dispatch_semaphore_signal(workerSemaphore); }); dispatch_semaphore_wait(workerSemaphore, DISPATCH_TIME_FOREVER); @@ -250,8 +259,8 @@ static NSString *const kMessageCode = @"I-COR000001"; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" aslmsg query = asl_new(ASL_TYPE_QUERY); - asl_set_query(query, ASL_KEY_FACILITY, kFIRLoggerASLClientFacilityName, ASL_QUERY_OP_EQUAL); - aslresponse r = asl_search(getFIRLoggerClient(), query); + asl_set_query(query, ASL_KEY_FACILITY, kGULLoggerASLClientFacilityName, ASL_QUERY_OP_EQUAL); + aslresponse r = asl_search(getGULLoggerClient(), query); asl_free(query); aslmsg m; const char *val; diff --git a/Example/Core/Tests/FIRMutableDictionaryTest.m b/Example/Core/Tests/FIRMutableDictionaryTest.m index 56c079c..f6be760 100644 --- a/Example/Core/Tests/FIRMutableDictionaryTest.m +++ b/Example/Core/Tests/FIRMutableDictionaryTest.m @@ -14,7 +14,7 @@ #import "FIRTestCase.h" -#import +#import const static NSString *const kKey = @"testKey1"; const static NSString *const kValue = @"testValue1"; @@ -22,14 +22,14 @@ const static NSString *const kKey2 = @"testKey2"; const static NSString *const kValue2 = @"testValue2"; @interface FIRMutableDictionaryTest : FIRTestCase -@property(nonatomic) FIRMutableDictionary *dictionary; +@property(nonatomic) GULMutableDictionary *dictionary; @end @implementation FIRMutableDictionaryTest - (void)setUp { [super setUp]; - self.dictionary = [[FIRMutableDictionary alloc] init]; + self.dictionary = [[GULMutableDictionary alloc] init]; } - (void)tearDown { diff --git a/Example/Core/Tests/FIRNetworkTest.m b/Example/Core/Tests/FIRNetworkTest.m index ba288d6..a1410ee 100644 --- a/Example/Core/Tests/FIRNetworkTest.m +++ b/Example/Core/Tests/FIRNetworkTest.m @@ -16,29 +16,29 @@ #import "GTMHTTPServer.h" -#import -#import -#import +#import +#import +#import -@interface FIRNetwork () +@interface GULNetwork () -- (void)reachability:(FIRReachabilityChecker *)reachability - statusChanged:(FIRReachabilityStatus)status; +- (void)reachability:(GULReachabilityChecker *)reachability + statusChanged:(GULReachabilityStatus)status; @end -@interface FIRNetworkURLSession () +@interface GULNetworkURLSession () - (void)maybeRemoveTempFilesAtURL:(NSURL *)tempFile expiringTime:(NSTimeInterval)expiringTime; @end -@interface FIRNetworkTest : FIRTestCase +@interface GULNetworkTest : FIRTestCase @end -@implementation FIRNetworkTest { +@implementation GULNetworkTest { dispatch_queue_t _backgroundQueue; - FIRNetwork *_network; + GULNetwork *_network; /// Fake Server. GTMHTTPServer *_httpServer; @@ -48,7 +48,7 @@ // For network reachability test. BOOL _fakeNetworkIsReachable; BOOL _currentNetworkStatus; - FIRReachabilityStatus _fakeReachabilityStatus; + GULReachabilityStatus _fakeReachabilityStatus; } #pragma mark - Setup and teardown @@ -66,7 +66,7 @@ NSError *error = nil; XCTAssertTrue([_httpServer start:&error], @"Failed to start HTTP server: %@", error); - _network = [[FIRNetwork alloc] init]; + _network = [[GULNetwork alloc] init]; _backgroundQueue = dispatch_queue_create("Test queue", DISPATCH_QUEUE_SERIAL); _request = nil; @@ -97,14 +97,14 @@ // Fake scenario with connectivity. _fakeNetworkIsReachable = YES; - _fakeReachabilityStatus = kFIRReachabilityViaWifi; + _fakeReachabilityStatus = kGULReachabilityViaWifi; [_network reachability:reachabilityMock statusChanged:[reachabilityMock reachabilityStatus]]; XCTAssertTrue([_network isNetworkConnected]); XCTAssertEqual(_currentNetworkStatus, _fakeNetworkIsReachable); // Fake scenario without connectivity. _fakeNetworkIsReachable = NO; - _fakeReachabilityStatus = kFIRReachabilityNotReachable; + _fakeReachabilityStatus = kGULReachabilityNotReachable; [_network reachability:reachabilityMock statusChanged:[reachabilityMock reachabilityStatus]]; XCTAssertFalse([_network isNetworkConnected]); XCTAssertEqual(_currentNetworkStatus, _fakeNetworkIsReachable); @@ -119,6 +119,7 @@ XCTestExpectation *expectation = [self expectationWithDescription:@"Expect block is called"]; NSData *uncompressedData = [@"Google" dataUsingEncoding:NSUTF8StringEncoding]; + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:%d/2", _httpServer.port]]; _statusCode = 200; @@ -181,7 +182,7 @@ queue:_backgroundQueue usingBackgroundSession:NO completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -203,7 +204,7 @@ queue:_backgroundQueue usingBackgroundSession:NO completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -367,7 +368,7 @@ queue:_backgroundQueue usingBackgroundSession:YES completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -389,7 +390,7 @@ queue:_backgroundQueue usingBackgroundSession:YES completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -551,7 +552,7 @@ queue:_backgroundQueue usingBackgroundSession:NO completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -572,7 +573,7 @@ queue:_backgroundQueue usingBackgroundSession:NO completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -746,7 +747,7 @@ queue:_backgroundQueue usingBackgroundSession:YES completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -767,7 +768,7 @@ queue:_backgroundQueue usingBackgroundSession:YES completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSError *error) { - XCTAssertEqual(error.code, FIRErrorCodeNetworkInvalidURL); + XCTAssertEqual(error.code, GULErrorCodeNetworkInvalidURL); XCTAssertFalse(_network.hasUploadInProgress, "There must be no pending request"); [expectation fulfill]; }]; @@ -881,14 +882,14 @@ NSError *writeError = nil; NSFileManager *fileManager = [NSFileManager defaultManager]; - FIRNetworkURLSession *session = [[FIRNetworkURLSession alloc] - initWithNetworkLoggerDelegate:(id)_network]; + GULNetworkURLSession *session = [[GULNetworkURLSession alloc] + initWithNetworkLoggerDelegate:(id)_network]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); NSString *applicationSupportDirectory = paths.firstObject; NSArray *tempPathComponents = @[ - applicationSupportDirectory, kFIRNetworkApplicationSupportSubdirectory, - @"FIRNetworkTemporaryDirectory" + applicationSupportDirectory, kGULNetworkApplicationSupportSubdirectory, + @"GULNetworkTemporaryDirectory" ]; NSURL *folderURL = [NSURL fileURLWithPathComponents:tempPathComponents]; [fileManager createDirectoryAtURL:folderURL @@ -946,7 +947,7 @@ // Test whether the request is compressed correctly. NSData *requestBody = [_request body]; - NSData *decompressedRequestData = [NSData gtm_dataByInflatingData:requestBody error:NULL]; + NSData *decompressedRequestData = [NSData gul_dataByInflatingGzippedData:requestBody error:NULL]; NSString *requestString = [[NSString alloc] initWithData:decompressedRequestData encoding:NSUTF8StringEncoding]; XCTAssertEqualObjects(requestString, @"Google", @"Request is not compressed correctly."); @@ -979,7 +980,7 @@ return _fakeNetworkIsReachable; } -- (FIRReachabilityStatus)reachabilityStatus { +- (GULReachabilityStatus)reachabilityStatus { return _fakeReachabilityStatus; } diff --git a/Example/Core/Tests/FIRReachabilityCheckerTest.m b/Example/Core/Tests/FIRReachabilityCheckerTest.m index 7b6b068..2f447d3 100644 --- a/Example/Core/Tests/FIRReachabilityCheckerTest.m +++ b/Example/Core/Tests/FIRReachabilityCheckerTest.m @@ -14,12 +14,12 @@ #import "FIRTestCase.h" -#import -#import +#import +#import "GULReachabilityChecker+Internal.h" -@interface FIRReachabilityCheckerTest : FIRTestCase { +@interface GULReachabilityCheckerTest : FIRTestCase { @private - FIRReachabilityChecker *checker_; + GULReachabilityChecker *checker_; NSMutableArray *statuses_; BOOL createFail_; BOOL setCallbackFail_; @@ -42,7 +42,7 @@ static NSString *const kHostname = @"www.google.com"; static const void *kFakeReachabilityObject = (const void *)0x8badf00d; -static FIRReachabilityCheckerTest *FakeReachabilityTest = nil; +static GULReachabilityCheckerTest *FakeReachabilityTest = nil; static struct { int callsMade; @@ -87,12 +87,12 @@ static void ReachabilityRelease(CFTypeRef reachability) { [FakeReachabilityTest releaseReachability:reachability]; } -static const struct FIRReachabilityApi kTestReachabilityApi = { +static const struct GULReachabilityApi kTestReachabilityApi = { ReachabilityCreateWithName, ReachabilitySetCallback, ReachabilityScheduleWithRunLoop, ReachabilityUnscheduleFromRunLoop, ReachabilityRelease, }; -@implementation FIRReachabilityCheckerTest +@implementation GULReachabilityCheckerTest - (void)resetFakeReachability { FakeReachabilityTest = self; @@ -114,9 +114,7 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { setCallbackFail_ = NO; scheduleUnscheduleFail_ = NO; - checker_ = [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self - loggerDelegate:nil - withHost:kHostname]; + checker_ = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:kHostname]; statuses_ = [[NSMutableArray alloc] init]; } @@ -180,8 +178,8 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { FakeReachability.releaseCall = ++FakeReachability.callsMade; } -- (void)reachability:(FIRReachabilityChecker *)reachability - statusChanged:(FIRReachabilityStatus)status { +- (void)reachability:(GULReachabilityChecker *)reachability + statusChanged:(GULReachabilityStatus)status { [statuses_ addObject:[NSNumber numberWithInt:(int)status]]; } @@ -192,28 +190,28 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { XCTAssertEqual([checker_ reachabilityApi], &kTestReachabilityApi, @""); XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertTrue([checker_ start], @""); XCTAssertTrue(checker_.isActive, @""); XCTAssertEqual([statuses_ count], (NSUInteger)0, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); FakeReachability.callback(kFakeReachabilityObject, 0, FakeReachability.callbackInfo); XCTAssertEqual([statuses_ count], (NSUInteger)1, @""); XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:0] intValue], - (int)kFIRReachabilityNotReachable, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityNotReachable, @""); + (int)kGULReachabilityNotReachable, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityNotReachable, @""); FakeReachability.callback(kFakeReachabilityObject, kSCNetworkReachabilityFlagsReachable, FakeReachability.callbackInfo); XCTAssertEqual([statuses_ count], (NSUInteger)2, @""); - XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:1] intValue], (int)kFIRReachabilityViaWifi, + XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:1] intValue], (int)kGULReachabilityViaWifi, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityViaWifi, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityViaWifi, @""); FakeReachability.callback( kFakeReachabilityObject, @@ -222,8 +220,8 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { XCTAssertEqual([statuses_ count], (NSUInteger)3, @""); XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:2] intValue], - (int)kFIRReachabilityNotReachable, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityNotReachable, @""); + (int)kGULReachabilityNotReachable, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityNotReachable, @""); #if TARGET_OS_IOS || TARGET_OS_TV FakeReachability.callback( @@ -233,22 +231,22 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { XCTAssertEqual([statuses_ count], (NSUInteger)4, @""); XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:3] intValue], - (int)kFIRReachabilityViaCellular, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityViaCellular, @""); + (int)kGULReachabilityViaCellular, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityViaCellular, @""); FakeReachability.callback(kFakeReachabilityObject, kSCNetworkReachabilityFlagsIsWWAN, FakeReachability.callbackInfo); XCTAssertEqual([statuses_ count], (NSUInteger)5, @""); XCTAssertEqual([(NSNumber *)[statuses_ objectAtIndex:4] intValue], - (int)kFIRReachabilityNotReachable, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityNotReachable, @""); + (int)kGULReachabilityNotReachable, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityNotReachable, @""); #endif [checker_ stop]; XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertEqual(FakeReachability.callsMade, 5, @""); @@ -266,17 +264,17 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { createFail_ = YES; XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertFalse([checker_ start], @""); XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); [checker_ stop]; XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertEqual(FakeReachability.callsMade, 1, @""); @@ -296,17 +294,17 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { setCallbackFail_ = YES; XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertFalse([checker_ start], @""); XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); [checker_ stop]; XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertEqual(FakeReachability.callsMade, 3, @""); @@ -326,17 +324,17 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { scheduleUnscheduleFail_ = YES; XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertFalse([checker_ start], @""); XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); [checker_ stop]; XCTAssertFalse(checker_.isActive, @""); - XCTAssertEqual(checker_.reachabilityStatus, kFIRReachabilityUnknown, @""); + XCTAssertEqual(checker_.reachabilityStatus, kGULReachabilityUnknown, @""); XCTAssertEqual(FakeReachability.callsMade, 4, @""); @@ -350,15 +348,9 @@ static const struct FIRReachabilityApi kTestReachabilityApi = { } - (void)testBadHost { - XCTAssertNil([[FIRReachabilityChecker alloc] - initWithReachabilityDelegate:self - loggerDelegate:(id)self - withHost:nil], + XCTAssertNil([[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:nil], @"Creating a checker with nil hostname must fail."); - XCTAssertNil([[FIRReachabilityChecker alloc] - initWithReachabilityDelegate:self - loggerDelegate:(id)self - withHost:@""], + XCTAssertNil([[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:@""], @"Creating a checker with empty hostname must fail."); } diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj index 8d3c13b..bf041f3 100644 --- a/Example/Firebase.xcodeproj/project.pbxproj +++ b/Example/Firebase.xcodeproj/project.pbxproj @@ -4887,6 +4887,10 @@ INFOPLIST_FILE = "$(SRCROOT)/Core/App/macOS/Core-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -4908,6 +4912,10 @@ INFOPLIST_FILE = "$(SRCROOT)/Core/App/macOS/Core-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -4922,10 +4930,14 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; - HEADER_SEARCH_PATHS = "$(inherited)"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../Firebase/Utilities/Reachability", + ); INFOPLIST_FILE = "Core/Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Tests-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -4942,10 +4954,14 @@ COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; - HEADER_SEARCH_PATHS = "$(inherited)"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../Firebase/Utilities/Reachability", + ); INFOPLIST_FILE = "Core/Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Tests-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -6267,6 +6283,10 @@ INFOPLIST_FILE = "$(SRCROOT)/Core/App/tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; @@ -6299,6 +6319,10 @@ INFOPLIST_FILE = "$(SRCROOT)/Core/App/tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + ); PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; @@ -6326,9 +6350,14 @@ DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = EQHXZ8M8AV; GCC_C_LANGUAGE_STANDARD = gnu11; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../Firebase/Utilities/Reachability", + ); INFOPLIST_FILE = "Core/Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; @@ -6358,9 +6387,14 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = EQHXZ8M8AV; GCC_C_LANGUAGE_STANDARD = gnu11; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../Firebase/Utilities/Reachability", + ); INFOPLIST_FILE = "Core/Tests/Tests-Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = appletvos; @@ -6650,6 +6684,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "${PODS_ROOT}/Headers/Private", + "$(SRCROOT)/../Firebase/Utilities/Reachability", ); INFOPLIST_FILE = "Core/Tests/Tests-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.2; @@ -6673,6 +6708,7 @@ HEADER_SEARCH_PATHS = ( "$(inherited)", "${PODS_ROOT}/Headers/Private", + "$(SRCROOT)/../Firebase/Utilities/Reachability", ); INFOPLIST_FILE = "Core/Tests/Tests-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 10.2; diff --git a/Example/Messaging/Tests/FIRMessagingClientTest.m b/Example/Messaging/Tests/FIRMessagingClientTest.m index 9c60d9c..738df05 100644 --- a/Example/Messaging/Tests/FIRMessagingClientTest.m +++ b/Example/Messaging/Tests/FIRMessagingClientTest.m @@ -31,7 +31,7 @@ #import "FIRMessagingUtilities.h" #import "NSError+FIRMessaging.h" -#import +#import static NSString *const kFIRMessagingUserDefaultsSuite = @"FIRMessagingClientTestUserDefaultsSuite"; @@ -104,7 +104,7 @@ static NSString *const kTopicToSubscribeTo = @"/topics/abcdef/hello-world"; [super setUp]; _mockClientDelegate = OCMStrictProtocolMock(@protocol(FIRMessagingClientDelegate)); - _mockReachability = OCMClassMock([FIRReachabilityChecker class]); + _mockReachability = OCMClassMock([GULReachabilityChecker class]); _mockRmqManager = OCMClassMock([FIRMessagingRmqManager class]); _client = [[FIRMessagingClient alloc] initWithDelegate:_mockClientDelegate reachability:_mockReachability @@ -203,7 +203,7 @@ static NSString *const kTopicToSubscribeTo = @"/topics/abcdef/hello-world"; // the network is available [[[self.mockReachability stub] - andReturnValue:@(kFIRReachabilityViaWifi)] reachabilityStatus]; + andReturnValue:@(kGULReachabilityViaWifi)] reachabilityStatus]; [self setupFakeConnectionWithClass:[FIRMessagingFakeFailConnection class] withSetupCompletionHandler:^(FIRMessagingConnection *connection) { @@ -241,7 +241,7 @@ static NSString *const kTopicToSubscribeTo = @"/topics/abcdef/hello-world"; // the network is available [[[self.mockReachability stub] - andReturnValue:@(kFIRReachabilityViaWifi)] reachabilityStatus]; + andReturnValue:@(kGULReachabilityViaWifi)] reachabilityStatus]; XCTestExpectation *setupConnection = [self expectationWithDescription:@"Fcm should successfully setup a connection"]; diff --git a/Example/Messaging/Tests/FIRMessagingConnectionTest.m b/Example/Messaging/Tests/FIRMessagingConnectionTest.m index b770cf5..71da301 100644 --- a/Example/Messaging/Tests/FIRMessagingConnectionTest.m +++ b/Example/Messaging/Tests/FIRMessagingConnectionTest.m @@ -19,7 +19,6 @@ #import #import "Protos/GtalkCore.pbobjc.h" -#import #import "FIRMessagingClient.h" #import "FIRMessagingConnection.h" @@ -374,7 +373,7 @@ static FIRMessagingProtoTag currentProtoSendTag; } - (void)_sendData:(NSData *)data withTag:(int8_t)tag rmqId:(NSString *)rmqId { - _GTMDevLog(@"FIRMessaging Socket: Send data with Tag: %d rmq: %@", tag, rmqId); + NSLog(@"FIRMessaging Socket: Send data with Tag: %d rmq: %@", tag, rmqId); if (currentProtoSendTag > 0) { XCTAssertEqual(tag, currentProtoSendTag); } diff --git a/Example/Messaging/Tests/FIRMessagingFakeSocket.m b/Example/Messaging/Tests/FIRMessagingFakeSocket.m index cffe69a..c347d31 100644 --- a/Example/Messaging/Tests/FIRMessagingFakeSocket.m +++ b/Example/Messaging/Tests/FIRMessagingFakeSocket.m @@ -18,7 +18,6 @@ #import "FIRMessagingConstants.h" #import "FIRMessagingDefines.h" -#import @interface FIRMessagingSecureSocket() @@ -78,7 +77,7 @@ } - (void)openStream:(NSStream *)stream { - _GTMDevAssert(stream, @"Cannot open nil stream"); + NSAssert(stream, @"Cannot open nil stream"); if (stream) { stream.delegate = self; [stream scheduleInRunLoop:self.runLoop forMode:NSDefaultRunLoopMode]; diff --git a/Example/Podfile b/Example/Podfile index 6c37a38..dc3acd0 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -5,6 +5,7 @@ use_frameworks! pod 'FirebaseCore', :path => '../' +pod 'GoogleUtilities', :path => '../' target 'Core_Example_iOS' do platform :ios, '8.0' @@ -12,7 +13,7 @@ target 'Core_Example_iOS' do # The next line is the forcing function for the Firebase pod. The Firebase # version's subspecs should depend on the component versions in their # corresponding podspec's. - pod 'Firebase/Core', '5.4.0' + pod 'Firebase/CoreOnly', '5.4.0' target 'Core_Tests_iOS' do inherit! :search_paths diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index 81008ca..691c6ce 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -19,10 +19,10 @@ #import "FIRAuth_Internal.h" #import -#import #import #import #import +#import #import "AuthProviders/EmailPassword/FIREmailPasswordAuthCredential.h" #import "FIRAdditionalUserInfo_Internal.h" @@ -443,7 +443,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; static Class applicationClass = nil; // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication // responds to it. - if (![FIRAppEnvironmentUtil isAppExtension]) { + if (![GULAppEnvironmentUtil isAppExtension]) { Class cls = NSClassFromString(@"UIApplication"); if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { applicationClass = cls; diff --git a/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m b/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m index 2b39aef..3e8dd90 100644 --- a/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m +++ b/Firebase/Auth/Source/FIRAuthAPNSTokenManager.m @@ -17,6 +17,8 @@ #import "FIRAuthAPNSTokenManager.h" #import +#import + #import "FIRAuthAPNSToken.h" #import "FIRAuthGlobalWorkQueue.h" @@ -137,42 +139,20 @@ static const NSTimeInterval kLegacyRegistrationTimeout = 30; NSError *error = nil; - Class envClass = NSClassFromString(@"FIRAppEnvironmentUtil"); - SEL isSimulatorSelector = NSSelectorFromString(@"isSimulator"); - if ([envClass respondsToSelector:isSimulatorSelector]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - if ([envClass performSelector:isSimulatorSelector]) { -#pragma clang diagnostic pop - FIRLogInfo(kFIRLoggerAuth, @"I-AUT000006", - @"Assuming prod APNs token type on simulator."); - return defaultAppTypeProd; - } + if ([GULAppEnvironmentUtil isSimulator]) { + FIRLogInfo(kFIRLoggerAuth, @"I-AUT000006", @"Assuming prod APNs token type on simulator."); + return defaultAppTypeProd; } - NSString *path = [[[NSBundle mainBundle] bundlePath] - stringByAppendingPathComponent:@"embedded.mobileprovision"]; - // Apps distributed via AppStore or TestFlight use the Production APNS certificates. - SEL isFromAppStoreSelector = NSSelectorFromString(@"isFromAppStore"); - if ([envClass respondsToSelector:isFromAppStoreSelector]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - if ([envClass performSelector:isFromAppStoreSelector]) { -#pragma clang diagnostic pop - return defaultAppTypeProd; - } + if ([GULAppEnvironmentUtil isFromAppStore]) { + return defaultAppTypeProd; } - - SEL isAppStoreReceiptSandboxSelector = NSSelectorFromString(@"isAppStoreReceiptSandbox"); - if ([envClass respondsToSelector:isAppStoreReceiptSandboxSelector]) { -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" - if ([envClass performSelector:isAppStoreReceiptSandboxSelector] && !path.length) { -#pragma clang diagnostic pop - // Distributed via TestFlight - return defaultAppTypeProd; - } + NSString *path = [[[NSBundle mainBundle] bundlePath] + stringByAppendingPathComponent:@"embedded.mobileprovision"]; + if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox] && !path.length) { + // Distributed via TestFlight + return defaultAppTypeProd; } NSMutableData *profileData = [NSMutableData dataWithContentsOfFile:path options:0 error:&error]; diff --git a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m index 73fbfda..d97fedc 100644 --- a/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m +++ b/Firebase/Auth/Source/FIRAuthAppDelegateProxy.m @@ -16,7 +16,7 @@ #import "FIRAuthAppDelegateProxy.h" -#import +#import #import @@ -206,7 +206,7 @@ static BOOL isIOS9orLater() { // responds to it. static Class applicationClass = nil; dispatch_once(&onceToken, ^{ - if (![FIRAppEnvironmentUtil isAppExtension]) { + if (![GULAppEnvironmentUtil isAppExtension]) { Class cls = NSClassFromString(@"UIApplication"); if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { applicationClass = cls; diff --git a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m index e5aa7f4..fb8b457 100644 --- a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m +++ b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m @@ -16,7 +16,7 @@ #import "FIRAuthDefaultUIDelegate.h" -#import +#import NS_ASSUME_NONNULL_BEGIN @@ -63,7 +63,7 @@ NS_ASSUME_NONNULL_BEGIN // iOS App extensions should not call [UIApplication sharedApplication], even if UIApplication // responds to it. static Class applicationClass = nil; - if (![FIRAppEnvironmentUtil isAppExtension]) { + if (![GULAppEnvironmentUtil isAppExtension]) { Class cls = NSClassFromString(@"UIApplication"); if (cls && [cls respondsToSelector:NSSelectorFromString(@"sharedApplication")]) { applicationClass = cls; diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m index 2ea7f6b..9f698cf 100644 --- a/Firebase/Core/FIRApp.m +++ b/Firebase/Core/FIRApp.m @@ -23,7 +23,6 @@ #import "Private/FIRCoreConfigurable.h" #import "Private/FIRLogger.h" #import "Private/FIROptionsInternal.h" -#import "third_party/FIRAppEnvironmentUtil.h" NSString *const kFIRServiceAdMob = @"AdMob"; NSString *const kFIRServiceAuth = @"Auth"; diff --git a/Firebase/Core/FIRLogger.m b/Firebase/Core/FIRLogger.m index 2784ae9..dbec728 100644 --- a/Firebase/Core/FIRLogger.m +++ b/Firebase/Core/FIRLogger.m @@ -14,16 +14,11 @@ #import "Private/FIRLogger.h" -#import "FIRLoggerLevel.h" -#import "Private/FIRVersion.h" -#import "third_party/FIRAppEnvironmentUtil.h" +#import +#import +#import -#include -#include -#include -#include -#include -#include +#import "Private/FIRVersion.h" FIRLoggerService kFIRLoggerABTesting = @"[Firebase/ABTesting]"; FIRLoggerService kFIRLoggerAdMob = @"[Firebase/AdMob]"; @@ -51,24 +46,11 @@ NSString *const kFIRLoggerForceSDTERRApplicationArgument = @"-FIRLoggerForceSTDE /// Key for the debug mode bit in NSUserDefaults. NSString *const kFIRPersistedDebugModeKey = @"/google/firebase/debug_mode"; -/// ASL client facility name used by FIRLogger. -const char *kFIRLoggerASLClientFacilityName = "com.firebase.app.logger"; - -/// Keys for the number of errors and warnings logged. -NSString *const kFIRLoggerErrorCountKey = @"/google/firebase/count_of_errors_logged"; -NSString *const kFIRLoggerWarningCountKey = @"/google/firebase/count_of_warnings_logged"; - -static dispatch_once_t sFIRLoggerOnceToken; - -static aslclient sFIRLoggerClient; - -static dispatch_queue_t sFIRClientQueue; - /// NSUserDefaults that should be used to store and read variables. If nil, `standardUserDefaults` /// will be used. static NSUserDefaults *sFIRLoggerUserDefaults; -static BOOL sFIRLoggerDebugMode; +static dispatch_once_t sFIRLoggerOnceToken; // The sFIRAnalyticsDebugMode flag is here to support the -FIRDebugEnabled/-FIRDebugDisabled // flags used by Analytics. Users who use those flags expect Analytics to log verbosely, @@ -76,8 +58,6 @@ static BOOL sFIRLoggerDebugMode; // that behavior. static BOOL sFIRAnalyticsDebugMode; -static FIRLoggerLevel sFIRLoggerMaximumLevel; - #ifdef DEBUG /// The regex pattern for the message code. static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; @@ -86,96 +66,53 @@ static NSRegularExpression *sMessageCodeRegex; void FIRLoggerInitializeASL() { dispatch_once(&sFIRLoggerOnceToken, ^{ - NSInteger majorOSVersion = [[FIRAppEnvironmentUtil systemVersion] integerValue]; - uint32_t aslOptions = ASL_OPT_STDERR; -#if TARGET_OS_SIMULATOR - // The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag. - if (majorOSVersion >= 11) { - aslOptions = 0; - } -#else - // Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag. - if (majorOSVersion >= 10) { - aslOptions = 0; - } -#endif // TARGET_OS_SIMULATOR + // Register Firebase Version with GULLogger. + GULLoggerRegisterVersion(FIRVersionString); // Override the aslOptions to ASL_OPT_STDERR if the override argument is passed in. NSArray *arguments = [NSProcessInfo processInfo].arguments; - if ([arguments containsObject:kFIRLoggerForceSDTERRApplicationArgument]) { - aslOptions = ASL_OPT_STDERR; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated - // Initialize the ASL client handle. - sFIRLoggerClient = asl_open(NULL, kFIRLoggerASLClientFacilityName, aslOptions); - - // Set the filter used by system/device log. Initialize in default mode. - asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE)); - sFIRLoggerDebugMode = NO; - sFIRAnalyticsDebugMode = NO; - sFIRLoggerMaximumLevel = FIRLoggerLevelNotice; + BOOL overrideSTDERR = [arguments containsObject:kFIRLoggerForceSDTERRApplicationArgument]; // Use the standard NSUserDefaults if it hasn't been explicitly set. if (sFIRLoggerUserDefaults == nil) { sFIRLoggerUserDefaults = [NSUserDefaults standardUserDefaults]; } + BOOL forceDebugMode = NO; BOOL debugMode = [sFIRLoggerUserDefaults boolForKey:kFIRPersistedDebugModeKey]; if ([arguments containsObject:kFIRDisableDebugModeApplicationArgument]) { // Default mode [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; } else if ([arguments containsObject:kFIREnableDebugModeApplicationArgument] || debugMode) { // Debug mode [sFIRLoggerUserDefaults setBool:YES forKey:kFIRPersistedDebugModeKey]; - asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); - sFIRLoggerDebugMode = YES; - } - - // We should disable debug mode if we are running from App Store. - if (sFIRLoggerDebugMode && [FIRAppEnvironmentUtil isFromAppStore]) { - sFIRLoggerDebugMode = NO; + forceDebugMode = YES; } - - sFIRClientQueue = dispatch_queue_create("FIRLoggingClientQueue", DISPATCH_QUEUE_SERIAL); - dispatch_set_target_queue(sFIRClientQueue, - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); - -#ifdef DEBUG - sMessageCodeRegex = - [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 error:NULL]; -#endif + GULLoggerInitializeASL(overrideSTDERR, forceDebugMode); }); } -void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode) { - FIRLoggerInitializeASL(); - dispatch_async(sFIRClientQueue, ^{ - // We should not enable debug mode if we are running from App Store. - if (analyticsDebugMode && [FIRAppEnvironmentUtil isFromAppStore]) { - return; - } - sFIRAnalyticsDebugMode = analyticsDebugMode; - asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); - }); +__attribute__((no_sanitize("thread"))) void FIRSetAnalyticsDebugMode(BOOL analyticsDebugMode) { + sFIRAnalyticsDebugMode = analyticsDebugMode; } void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) { - if (loggerLevel < FIRLoggerLevelMin || loggerLevel > FIRLoggerLevelMax) { - FIRLogError(kFIRLoggerCore, @"I-COR000023", @"Invalid logger level, %ld", (long)loggerLevel); - return; - } FIRLoggerInitializeASL(); - // We should not raise the logger level if we are running from App Store. - if (loggerLevel >= FIRLoggerLevelNotice && [FIRAppEnvironmentUtil isFromAppStore]) { - return; - } + GULSetLoggerLevel((GULLoggerLevel)loggerLevel); +} - sFIRLoggerMaximumLevel = loggerLevel; - dispatch_async(sFIRClientQueue, ^{ - asl_set_filter(sFIRLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel)); - }); +#ifdef DEBUG +void FIRResetLogger() { + extern void GULResetLogger(void); + sFIRLoggerOnceToken = 0; + [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; + sFIRLoggerUserDefaults = nil; + GULResetLogger(); +} + +void FIRSetLoggerUserDefaults(NSUserDefaults *defaults) { + sFIRLoggerUserDefaults = defaults; } +#endif /** * Check if the level is high enough to be loggable. @@ -186,37 +123,11 @@ void FIRSetLoggerLevel(FIRLoggerLevel loggerLevel) { __attribute__((no_sanitize("thread"))) BOOL FIRIsLoggableLevel(FIRLoggerLevel loggerLevel, BOOL analyticsComponent) { FIRLoggerInitializeASL(); - if (sFIRLoggerDebugMode) { - return YES; - } else if (sFIRAnalyticsDebugMode && analyticsComponent) { + if (sFIRAnalyticsDebugMode && analyticsComponent) { return YES; } - return (BOOL)(loggerLevel <= sFIRLoggerMaximumLevel); -} - -#ifdef DEBUG -void FIRResetLogger() { - sFIRLoggerOnceToken = 0; - [sFIRLoggerUserDefaults removeObjectForKey:kFIRPersistedDebugModeKey]; - sFIRLoggerUserDefaults = nil; -} - -void FIRSetLoggerUserDefaults(NSUserDefaults *defaults) { - sFIRLoggerUserDefaults = defaults; -} - -aslclient getFIRLoggerClient() { - return sFIRLoggerClient; -} - -dispatch_queue_t getFIRClientQueue() { - return sFIRClientQueue; -} - -BOOL getFIRLoggerDebugMode() { - return sFIRLoggerDebugMode; + return GULIsLoggableLevel((GULLoggerLevel)loggerLevel); } -#endif void FIRLogBasic(FIRLoggerLevel level, FIRLoggerService service, @@ -224,32 +135,10 @@ void FIRLogBasic(FIRLoggerLevel level, NSString *message, va_list args_ptr) { FIRLoggerInitializeASL(); - BOOL canLog = level <= sFIRLoggerMaximumLevel; - - if (sFIRLoggerDebugMode) { - canLog = YES; - } else if (sFIRAnalyticsDebugMode && [kFIRLoggerAnalytics isEqualToString:service]) { - canLog = YES; - } - - if (!canLog) { - return; - } -#ifdef DEBUG - NSCAssert(messageCode.length == 11, @"Incorrect message code length."); - NSRange messageCodeRange = NSMakeRange(0, messageCode.length); - NSUInteger numberOfMatches = - [sMessageCodeRegex numberOfMatchesInString:messageCode options:0 range:messageCodeRange]; - NSCAssert(numberOfMatches == 1, @"Incorrect message code format."); -#endif - NSString *logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr]; - logMsg = - [NSString stringWithFormat:@"%s - %@[%@] %@", FIRVersionString, service, messageCode, logMsg]; - dispatch_async(sFIRClientQueue, ^{ - asl_log(sFIRLoggerClient, NULL, level, "%s", logMsg.UTF8String); - }); + GULLogBasic((GULLoggerLevel)level, service, + sFIRAnalyticsDebugMode && [kFIRLoggerAnalytics isEqualToString:service], messageCode, + message, args_ptr); } -#pragma clang diagnostic pop /** * Generates the logging functions using macros. diff --git a/Firebase/Core/FIRMutableDictionary.m b/Firebase/Core/FIRMutableDictionary.m deleted file mode 100644 index 31941bc..0000000 --- a/Firebase/Core/FIRMutableDictionary.m +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2017 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "Private/FIRMutableDictionary.h" - -@implementation FIRMutableDictionary { - /// The mutable dictionary. - NSMutableDictionary *_objects; - - /// Serial synchronization queue. All reads should use dispatch_sync, while writes use - /// dispatch_async. - dispatch_queue_t _queue; -} - -- (instancetype)init { - self = [super init]; - - if (self) { - _objects = [[NSMutableDictionary alloc] init]; - _queue = dispatch_queue_create("FIRMutableDictionary", DISPATCH_QUEUE_SERIAL); - } - - return self; -} - -- (NSString *)description { - __block NSString *description; - dispatch_sync(_queue, ^{ - description = self->_objects.description; - }); - return description; -} - -- (id)objectForKey:(id)key { - __block id object; - dispatch_sync(_queue, ^{ - object = self->_objects[key]; - }); - return object; -} - -- (void)setObject:(id)object forKey:(id)key { - dispatch_async(_queue, ^{ - self->_objects[key] = object; - }); -} - -- (void)removeObjectForKey:(id)key { - dispatch_async(_queue, ^{ - [self->_objects removeObjectForKey:key]; - }); -} - -- (void)removeAllObjects { - dispatch_async(_queue, ^{ - [self->_objects removeAllObjects]; - }); -} - -- (NSUInteger)count { - __block NSUInteger count; - dispatch_sync(_queue, ^{ - count = self->_objects.count; - }); - return count; -} - -- (id)objectForKeyedSubscript:(id)key { - // The method this calls is already synchronized. - return [self objectForKey:key]; -} - -- (void)setObject:(id)obj forKeyedSubscript:(id)key { - // The method this calls is already synchronized. - [self setObject:obj forKey:key]; -} - -- (NSDictionary *)dictionary { - __block NSDictionary *dictionary; - dispatch_sync(_queue, ^{ - dictionary = [self->_objects copy]; - }); - return dictionary; -} - -@end diff --git a/Firebase/Core/FIRNetwork.m b/Firebase/Core/FIRNetwork.m deleted file mode 100644 index ff292fc..0000000 --- a/Firebase/Core/FIRNetwork.m +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2017 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "Private/FIRNetwork.h" -#import "Private/FIRNetworkMessageCode.h" - -#import "Private/FIRLogger.h" -#import "Private/FIRMutableDictionary.h" -#import "Private/FIRNetworkConstants.h" -#import "Private/FIRReachabilityChecker.h" - -#import - -/// Constant string for request header Content-Encoding. -static NSString *const kFIRNetworkContentCompressionKey = @"Content-Encoding"; - -/// Constant string for request header Content-Encoding value. -static NSString *const kFIRNetworkContentCompressionValue = @"gzip"; - -/// Constant string for request header Content-Length. -static NSString *const kFIRNetworkContentLengthKey = @"Content-Length"; - -/// Constant string for request header Content-Type. -static NSString *const kFIRNetworkContentTypeKey = @"Content-Type"; - -/// Constant string for request header Content-Type value. -static NSString *const kFIRNetworkContentTypeValue = @"application/x-www-form-urlencoded"; - -/// Constant string for GET request method. -static NSString *const kFIRNetworkGETRequestMethod = @"GET"; - -/// Constant string for POST request method. -static NSString *const kFIRNetworkPOSTRequestMethod = @"POST"; - -/// Default constant string as a prefix for network logger. -static NSString *const kFIRNetworkLogTag = @"Firebase/Network"; - -@interface FIRNetwork () -@end - -@implementation FIRNetwork { - /// Network reachability. - FIRReachabilityChecker *_reachability; - - /// The dictionary of requests by session IDs { NSString : id }. - FIRMutableDictionary *_requests; -} - -- (instancetype)init { - return [self initWithReachabilityHost:kFIRNetworkReachabilityHost]; -} - -- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost { - self = [super init]; - if (self) { - // Setup reachability. - _reachability = [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self - loggerDelegate:self - withHost:reachabilityHost]; - if (![_reachability start]) { - return nil; - } - - _requests = [[FIRMutableDictionary alloc] init]; - _timeoutInterval = kFIRNetworkTimeOutInterval; - } - return self; -} - -- (void)dealloc { - _reachability.reachabilityDelegate = nil; - [_reachability stop]; -} - -#pragma mark - External Methods - -+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID - completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler { - [FIRNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID - completionHandler:completionHandler]; -} - -- (NSString *)postURL:(NSURL *)url - payload:(NSData *)payload - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(FIRNetworkCompletionHandler)handler { - if (!url.absoluteString.length) { - [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; - return nil; - } - - NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval; - - NSMutableURLRequest *request = - [[NSMutableURLRequest alloc] initWithURL:url - cachePolicy:NSURLRequestReloadIgnoringLocalCacheData - timeoutInterval:timeOutInterval]; - - if (!request) { - [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation - queue:queue - withHandler:handler]; - return nil; - } - - NSError *compressError = nil; - NSData *compressedData = [NSData gtm_dataByGzippingData:payload error:&compressError]; - if (!compressedData || compressError) { - if (compressError || payload.length > 0) { - // If the payload is not empty but it fails to compress the payload, something has been wrong. - [self handleErrorWithCode:FIRErrorCodeNetworkPayloadCompression - queue:queue - withHandler:handler]; - return nil; - } - compressedData = [[NSData alloc] init]; - } - - NSString *postLength = @(compressedData.length).stringValue; - - // Set up the request with the compressed data. - [request setValue:postLength forHTTPHeaderField:kFIRNetworkContentLengthKey]; - request.HTTPBody = compressedData; - request.HTTPMethod = kFIRNetworkPOSTRequestMethod; - [request setValue:kFIRNetworkContentTypeValue forHTTPHeaderField:kFIRNetworkContentTypeKey]; - [request setValue:kFIRNetworkContentCompressionValue - forHTTPHeaderField:kFIRNetworkContentCompressionKey]; - - FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; - fetcher.backgroundNetworkEnabled = usingBackgroundSession; - - __weak FIRNetwork *weakSelf = self; - NSString *requestID = [fetcher - sessionIDFromAsyncPOSTRequest:request - completionHandler:^(NSHTTPURLResponse *response, NSData *data, - NSString *sessionID, NSError *error) { - FIRNetwork *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); - dispatch_async(queueToDispatch, ^{ - if (sessionID.length) { - [strongSelf->_requests removeObjectForKey:sessionID]; - } - if (handler) { - handler(response, data, error); - } - }); - }]; - if (!requestID) { - [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation - queue:queue - withHandler:handler]; - return nil; - } - - [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeNetwork000 - message:@"Uploading data. Host" - context:url]; - _requests[requestID] = fetcher; - return requestID; -} - -- (NSString *)getURL:(NSURL *)url - headers:(NSDictionary *)headers - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(FIRNetworkCompletionHandler)handler { - if (!url.absoluteString.length) { - [self handleErrorWithCode:FIRErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; - return nil; - } - - NSTimeInterval timeOutInterval = _timeoutInterval ?: kFIRNetworkTimeOutInterval; - NSMutableURLRequest *request = - [[NSMutableURLRequest alloc] initWithURL:url - cachePolicy:NSURLRequestReloadIgnoringLocalCacheData - timeoutInterval:timeOutInterval]; - - if (!request) { - [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation - queue:queue - withHandler:handler]; - return nil; - } - - request.HTTPMethod = kFIRNetworkGETRequestMethod; - request.allHTTPHeaderFields = headers; - - FIRNetworkURLSession *fetcher = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; - fetcher.backgroundNetworkEnabled = usingBackgroundSession; - - __weak FIRNetwork *weakSelf = self; - NSString *requestID = [fetcher - sessionIDFromAsyncGETRequest:request - completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID, - NSError *error) { - FIRNetwork *strongSelf = weakSelf; - if (!strongSelf) { - return; - } - dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); - dispatch_async(queueToDispatch, ^{ - if (sessionID.length) { - [strongSelf->_requests removeObjectForKey:sessionID]; - } - if (handler) { - handler(response, data, error); - } - }); - }]; - - if (!requestID) { - [self handleErrorWithCode:FIRErrorCodeNetworkSessionTaskCreation - queue:queue - withHandler:handler]; - return nil; - } - - [self firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeNetwork001 - message:@"Downloading data. Host" - context:url]; - _requests[requestID] = fetcher; - return requestID; -} - -- (BOOL)hasUploadInProgress { - return _requests.count > 0; -} - -#pragma mark - Network Reachability - -/// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network -/// reachability has changed. -- (void)reachability:(FIRReachabilityChecker *)reachability - statusChanged:(FIRReachabilityStatus)status { - _networkConnected = (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi); - [_reachabilityDelegate reachabilityDidChange]; -} - -#pragma mark - Network logger delegate - -- (void)setLoggerDelegate:(id)loggerDelegate { - // Explicitly check whether the delegate responds to the methods because conformsToProtocol does - // not work correctly even though the delegate does respond to the methods. - if (!loggerDelegate || - ![loggerDelegate - respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:contexts:)] || - ![loggerDelegate - respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:context:)] || - ! - [loggerDelegate respondsToSelector:@selector(firNetwork_logWithLevel:messageCode:message:)]) { - FIRLogError(kFIRLoggerAnalytics, - [NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork002], - @"Cannot set the network logger delegate: delegate does not conform to the network " - "logger protocol."); - return; - } - _loggerDelegate = loggerDelegate; -} - -#pragma mark - Private methods - -/// Handles network error and calls completion handler with the error. -- (void)handleErrorWithCode:(NSInteger)code - queue:(dispatch_queue_t)queue - withHandler:(FIRNetworkCompletionHandler)handler { - NSDictionary *userInfo = @{kFIRNetworkErrorContext : @"Failed to create network request"}; - NSError *error = - [[NSError alloc] initWithDomain:kFIRNetworkErrorDomain code:code userInfo:userInfo]; - [self firNetwork_logWithLevel:kFIRNetworkLogLevelWarning - messageCode:kFIRNetworkMessageCodeNetwork002 - message:@"Failed to create network request. Code, error" - contexts:@[ @(code), error ]]; - if (handler) { - dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); - dispatch_async(queueToDispatch, ^{ - handler(nil, nil, error); - }); - } -} - -#pragma mark - Network logger - -- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel - messageCode:(FIRNetworkMessageCode)messageCode - message:(NSString *)message - contexts:(NSArray *)contexts { - // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log - // errors/warnings/info messages to the console log. - if (_loggerDelegate) { - [_loggerDelegate firNetwork_logWithLevel:logLevel - messageCode:messageCode - message:message - contexts:contexts]; - return; - } - if (_isDebugModeEnabled || logLevel == kFIRNetworkLogLevelError || - logLevel == kFIRNetworkLogLevelWarning || logLevel == kFIRNetworkLogLevelInfo) { - NSString *formattedMessage = FIRStringWithLogMessage(message, logLevel, contexts); - NSLog(@"%@", formattedMessage); - FIRLogBasic((FIRLoggerLevel)logLevel, kFIRLoggerCore, - [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage, - NULL); - } -} - -- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel - messageCode:(FIRNetworkMessageCode)messageCode - message:(NSString *)message - context:(id)context { - if (_loggerDelegate) { - [_loggerDelegate firNetwork_logWithLevel:logLevel - messageCode:messageCode - message:message - context:context]; - return; - } - NSArray *contexts = context ? @[ context ] : @[]; - [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts]; -} - -- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel - messageCode:(FIRNetworkMessageCode)messageCode - message:(NSString *)message { - if (_loggerDelegate) { - [_loggerDelegate firNetwork_logWithLevel:logLevel messageCode:messageCode message:message]; - return; - } - [self firNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]]; -} - -/// Returns a string for the given log level (e.g. kFIRNetworkLogLevelError -> @"ERROR"). -static NSString *FIRLogLevelDescriptionFromLogLevel(FIRNetworkLogLevel logLevel) { - static NSDictionary *levelNames = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - levelNames = @{ - @(kFIRNetworkLogLevelError) : @"ERROR", - @(kFIRNetworkLogLevelWarning) : @"WARNING", - @(kFIRNetworkLogLevelInfo) : @"INFO", - @(kFIRNetworkLogLevelDebug) : @"DEBUG" - }; - }); - return levelNames[@(logLevel)]; -} - -/// Returns a formatted string to be used for console logging. -static NSString *FIRStringWithLogMessage(NSString *message, - FIRNetworkLogLevel logLevel, - NSArray *contexts) { - if (!message) { - message = @"(Message was nil)"; - } else if (!message.length) { - message = @"(Message was empty)"; - } - NSMutableString *result = [[NSMutableString alloc] - initWithFormat:@"<%@/%@> %@", kFIRNetworkLogTag, FIRLogLevelDescriptionFromLogLevel(logLevel), - message]; - - if (!contexts.count) { - return result; - } - - NSMutableArray *formattedContexts = [[NSMutableArray alloc] init]; - for (id item in contexts) { - [formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")]; - } - - [result appendString:@": "]; - [result appendString:[formattedContexts componentsJoinedByString:@", "]]; - return result; -} - -@end diff --git a/Firebase/Core/FIRNetworkConstants.m b/Firebase/Core/FIRNetworkConstants.m deleted file mode 100644 index c958201..0000000 --- a/Firebase/Core/FIRNetworkConstants.m +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import "Private/FIRNetworkConstants.h" - -#import - -NSString *const kFIRNetworkBackgroundSessionConfigIDPrefix = - @"com.firebase.network.background-upload"; -NSString *const kFIRNetworkApplicationSupportSubdirectory = @"Firebase/Network"; -NSString *const kFIRNetworkTempDirectoryName = @"FIRNetworkTemporaryDirectory"; -const NSTimeInterval kFIRNetworkTempFolderExpireTime = 60 * 60; // 1 hour -const NSTimeInterval kFIRNetworkTimeOutInterval = 60; // 1 minute. -NSString *const kFIRNetworkReachabilityHost = @"app-measurement.com"; -NSString *const kFIRNetworkErrorContext = @"Context"; - -const int kFIRNetworkHTTPStatusOK = 200; -const int kFIRNetworkHTTPStatusNoContent = 204; -const int kFIRNetworkHTTPStatusCodeMultipleChoices = 300; -const int kFIRNetworkHTTPStatusCodeMovedPermanently = 301; -const int kFIRNetworkHTTPStatusCodeFound = 302; -const int kFIRNetworkHTTPStatusCodeNotModified = 304; -const int kFIRNetworkHTTPStatusCodeMovedTemporarily = 307; -const int kFIRNetworkHTTPStatusCodeNotFound = 404; -const int kFIRNetworkHTTPStatusCodeCannotAcceptTraffic = 429; -const int kFIRNetworkHTTPStatusCodeUnavailable = 503; - -NSString *const kFIRNetworkErrorDomain = @"com.firebase.network.ErrorDomain"; diff --git a/Firebase/Core/FIRNetworkURLSession.m b/Firebase/Core/FIRNetworkURLSession.m deleted file mode 100644 index 470d3e9..0000000 --- a/Firebase/Core/FIRNetworkURLSession.m +++ /dev/null @@ -1,669 +0,0 @@ -// Copyright 2017 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import "Private/FIRNetworkURLSession.h" - -#import "Private/FIRLogger.h" -#import "Private/FIRMutableDictionary.h" -#import "Private/FIRNetworkConstants.h" -#import "Private/FIRNetworkMessageCode.h" - -@implementation FIRNetworkURLSession { - /// The handler to be called when the request completes or error has occurs. - FIRNetworkURLSessionCompletionHandler _completionHandler; - - /// Session ID generated randomly with a fixed prefix. - NSString *_sessionID; - - /// The session configuration. - NSURLSessionConfiguration *_sessionConfig; - - /// The path to the directory where all temporary files are stored before uploading. - NSURL *_networkDirectoryURL; - - /// The downloaded data from fetching. - NSData *_downloadedData; - - /// The path to the temporary file which stores the uploading data. - NSURL *_uploadingFileURL; - - /// The current request. - NSURLRequest *_request; -} - -#pragma mark - Init - -- (instancetype)initWithNetworkLoggerDelegate:(id)networkLoggerDelegate { - self = [super init]; - if (self) { - // Create URL to the directory where all temporary files to upload have to be stored. - NSArray *paths = - NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); - NSString *applicationSupportDirectory = paths.firstObject; - NSArray *tempPathComponents = @[ - applicationSupportDirectory, kFIRNetworkApplicationSupportSubdirectory, - kFIRNetworkTempDirectoryName - ]; - _networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents]; - _sessionID = [NSString stringWithFormat:@"%@-%@", kFIRNetworkBackgroundSessionConfigIDPrefix, - [[NSUUID UUID] UUIDString]]; - _loggerDelegate = networkLoggerDelegate; - } - return self; -} - -#pragma mark - External Methods - -#pragma mark - To be called from AppDelegate - -+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID - completionHandler: - (FIRNetworkSystemCompletionHandler)systemCompletionHandler { - // The session may not be FIRAnalytics background. Ignore those that do not have the prefix. - if (![sessionID hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) { - return; - } - FIRNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID]; - if (fetcher != nil) { - [fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID]; - } else { - FIRLogError(kFIRLoggerCore, - [NSString stringWithFormat:@"I-NET%06ld", (long)kFIRNetworkMessageCodeNetwork003], - @"Failed to retrieve background session with ID %@ after app is relaunched.", - sessionID); - } -} - -#pragma mark - External Methods - -/// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the -/// connection. -- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request - completionHandler:(FIRNetworkURLSessionCompletionHandler)handler { - // NSURLSessionUploadTask does not work with NSData in the background. - // To avoid this issue, write the data to a temporary file to upload it. - // Make a temporary file with the data subset. - _uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID]; - NSError *writeError; - NSURLSessionUploadTask *postRequestTask; - NSURLSession *session; - BOOL didWriteFile = NO; - - // Clean up the entire temp folder to avoid temp files that remain in case the previous session - // crashed and did not clean up. - [self maybeRemoveTempFilesAtURL:_networkDirectoryURL - expiringTime:kFIRNetworkTempFolderExpireTime]; - - // If there is no background network enabled, no need to write to file. This will allow default - // network session which runs on the foreground. - if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) { - didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path - options:NSDataWritingAtomic - error:&writeError]; - - if (writeError) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession000 - message:@"Failed to write request data to file" - context:writeError]; - } - } - - if (didWriteFile) { - // Exclude this file from backing up to iTunes. There are conflicting reports that excluding - // directory from backing up does not excluding files of that directory from backing up. - [self excludeFromBackupForURL:_uploadingFileURL]; - - _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; - [self populateSessionConfig:_sessionConfig withRequest:request]; - session = [NSURLSession sessionWithConfiguration:_sessionConfig - delegate:self - delegateQueue:[NSOperationQueue mainQueue]]; - postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL]; - } else { - // If we cannot write to file, just send it in the foreground. - _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; - [self populateSessionConfig:_sessionConfig withRequest:request]; - _sessionConfig.URLCache = nil; - session = [NSURLSession sessionWithConfiguration:_sessionConfig - delegate:self - delegateQueue:[NSOperationQueue mainQueue]]; - postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody]; - } - - if (!session || !postRequestTask) { - NSError *error = [[NSError alloc] - initWithDomain:kFIRNetworkErrorDomain - code:FIRErrorCodeNetworkRequestCreation - userInfo:@{kFIRNetworkErrorContext : @"Cannot create network session"}]; - [self callCompletionHandler:handler withResponse:nil data:nil error:error]; - return nil; - } - - // Save the session into memory. - NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap]; - [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID]; - - _request = [request copy]; - - // Store completion handler because background session does not accept handler block but custom - // delegate. - _completionHandler = [handler copy]; - [postRequestTask resume]; - - return _sessionID; -} - -/// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session. -- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request - completionHandler:(FIRNetworkURLSessionCompletionHandler)handler { - if (_backgroundNetworkEnabled) { - _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; - } else { - _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; - } - - [self populateSessionConfig:_sessionConfig withRequest:request]; - - // Do not cache the GET request. - _sessionConfig.URLCache = nil; - - NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig - delegate:self - delegateQueue:[NSOperationQueue mainQueue]]; - NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request]; - - if (!session || !downloadTask) { - NSError *error = [[NSError alloc] - initWithDomain:kFIRNetworkErrorDomain - code:FIRErrorCodeNetworkRequestCreation - userInfo:@{kFIRNetworkErrorContext : @"Cannot create network session"}]; - [self callCompletionHandler:handler withResponse:nil data:nil error:error]; - return nil; - } - - // Save the session into memory. - NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap]; - [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID]; - - _request = [request copy]; - - _completionHandler = [handler copy]; - [downloadTask resume]; - - return _sessionID; -} - -#pragma mark - NSURLSessionTaskDelegate - -/// Called by the NSURLSession once the download task is completed. The file is saved in the -/// provided URL so we need to read the data and store into _downloadedData. Once the session is -/// completed, URLSession:task:didCompleteWithError will be called and the completion handler will -/// be called with the downloaded data. -- (void)URLSession:(NSURLSession *)session - downloadTask:(NSURLSessionDownloadTask *)task - didFinishDownloadingToURL:(NSURL *)url { - if (!url.path) { - [_loggerDelegate - firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession001 - message:@"Unable to read downloaded data from empty temp path"]; - _downloadedData = nil; - return; - } - - NSError *error; - _downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error]; - - if (error) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession002 - message:@"Cannot read the content of downloaded data" - context:error]; - _downloadedData = nil; - } -} - -#if TARGET_OS_IOS || TARGET_OS_TV -- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeURLSession003 - message:@"Background session finished" - context:session.configuration.identifier]; - [self callSystemCompletionHandler:session.configuration.identifier]; -} -#endif - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - didCompleteWithError:(NSError *)error { - // Avoid any chance of recursive behavior leading to it being used repeatedly. - FIRNetworkURLSessionCompletionHandler handler = _completionHandler; - _completionHandler = nil; - - if (task.response) { - // The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7. - NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP"); - - // The server responded so ignore the error created by the system. - error = nil; - } else if (!error) { - error = [[NSError alloc] - initWithDomain:kFIRNetworkErrorDomain - code:FIRErrorCodeNetworkInvalidResponse - userInfo:@{kFIRNetworkErrorContext : @"Network Error: Empty network response"}]; - } - - [self callCompletionHandler:handler - withResponse:(NSHTTPURLResponse *)task.response - data:_downloadedData - error:error]; - - // Remove the temp file to avoid trashing devices with lots of temp files. - [self removeTempItemAtURL:_uploadingFileURL]; - - // Try to clean up stale files again. - [self maybeRemoveTempFilesAtURL:_networkDirectoryURL - expiringTime:kFIRNetworkTempFolderExpireTime]; -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential *credential))completionHandler { - // The handling is modeled after GTMSessionFetcher. - if ([challenge.protectionSpace.authenticationMethod - isEqualToString:NSURLAuthenticationMethodServerTrust]) { - SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; - if (serverTrust == NULL) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeURLSession004 - message:@"Received empty server trust for host. Host" - context:_request.URL]; - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); - return; - } - NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; - if (!credential) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning - messageCode:kFIRNetworkMessageCodeURLSession005 - message:@"Unable to verify server identity. Host" - context:_request.URL]; - completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); - return; - } - - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeURLSession006 - message:@"Received SSL challenge for host. Host" - context:_request.URL]; - - void (^callback)(BOOL) = ^(BOOL allow) { - if (allow) { - completionHandler(NSURLSessionAuthChallengeUseCredential, credential); - } else { - [self->_loggerDelegate - firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeURLSession007 - message:@"Cancelling authentication challenge for host. Host" - context:self->_request.URL]; - completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); - } - }; - - // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. - CFRetain(serverTrust); - - // Evaluate the certificate chain. - // - // The delegate queue may be the main thread. Trust evaluation could cause some - // blocking network activity, so we must evaluate async, as documented at - // https://developer.apple.com/library/ios/technotes/tn2232/ - dispatch_queue_t evaluateBackgroundQueue = - dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - - dispatch_async(evaluateBackgroundQueue, ^{ - SecTrustResultType trustEval = kSecTrustResultInvalid; - BOOL shouldAllow; - OSStatus trustError; - - @synchronized([FIRNetworkURLSession class]) { - trustError = SecTrustEvaluate(serverTrust, &trustEval); - } - - if (trustError != errSecSuccess) { - [self->_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession008 - message:@"Cannot evaluate server trust. Error, host" - contexts:@[ @(trustError), self->_request.URL ]]; - shouldAllow = NO; - } else { - // Having a trust level "unspecified" by the user is the usual result, described at - // https://developer.apple.com/library/mac/qa/qa1360 - shouldAllow = - (trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed); - } - - // Call the call back with the permission. - callback(shouldAllow); - - CFRelease(serverTrust); - }); - return; - } - - // Default handling for other Auth Challenges. - completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); -} - -#pragma mark - Internal Methods - -/// Stores system completion handler with session ID as key. -- (void)addSystemCompletionHandler:(FIRNetworkSystemCompletionHandler)handler - forSession:(NSString *)identifier { - if (!handler) { - [_loggerDelegate - firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession009 - message:@"Cannot store nil system completion handler in network"]; - return; - } - - if (!identifier.length) { - [_loggerDelegate - firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession010 - message: - @"Cannot store system completion handler with empty network " - "session identifier"]; - return; - } - - FIRMutableDictionary *systemCompletionHandlers = - [[self class] sessionIDToSystemCompletionHandlerDictionary]; - if (systemCompletionHandlers[identifier]) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelWarning - messageCode:kFIRNetworkMessageCodeURLSession011 - message:@"Got multiple system handlers for a single session ID" - context:identifier]; - } - - systemCompletionHandlers[identifier] = handler; -} - -/// Calls the system provided completion handler with the session ID stored in the dictionary. -/// The handler will be removed from the dictionary after being called. -- (void)callSystemCompletionHandler:(NSString *)identifier { - FIRMutableDictionary *systemCompletionHandlers = - [[self class] sessionIDToSystemCompletionHandlerDictionary]; - FIRNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier]; - - if (handler) { - [systemCompletionHandlers removeObjectForKey:identifier]; - - dispatch_async(dispatch_get_main_queue(), ^{ - handler(); - }); - } -} - -/// Sets or updates the session ID of this session. -- (void)setSessionID:(NSString *)sessionID { - _sessionID = [sessionID copy]; -} - -/// Creates a background session configuration with the session ID using the supported method. -- (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID { -#if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ - MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \ - TARGET_OS_TV || \ - (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) - - // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name. - return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; - -#elif (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ - MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \ - (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0) - - // Do a runtime check to avoid a deprecation warning about using - // +backgroundSessionConfiguration: on iOS 8. - if ([NSURLSessionConfiguration - respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { - // Running on iOS 8+/OS X 10.10+. - return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; - } else { - // Running on iOS 7/OS X 10.9. - return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; - } - -#else - // Building with an SDK earlier than iOS 8/OS X 10.10. - return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; -#endif -} - -- (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime { - if (!folderURL.absoluteString.length) { - return; - } - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error = nil; - - NSArray *properties = @[ NSURLCreationDateKey ]; - NSArray *directoryContent = - [fileManager contentsOfDirectoryAtURL:folderURL - includingPropertiesForKeys:properties - options:NSDirectoryEnumerationSkipsSubdirectoryDescendants - error:&error]; - if (error && error.code != NSFileReadNoSuchFileError) { - [_loggerDelegate - firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeURLSession012 - message:@"Cannot get files from the temporary network folder. Error" - context:error]; - return; - } - - if (!directoryContent.count) { - return; - } - - NSTimeInterval now = [NSDate date].timeIntervalSince1970; - for (NSURL *tempFile in directoryContent) { - NSDate *creationDate; - BOOL getCreationDate = - [tempFile getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL]; - if (!getCreationDate) { - continue; - } - NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970; - if (fabs(now - creationTimeInterval) > staleTime) { - [self removeTempItemAtURL:tempFile]; - } - } -} - -/// Removes the temporary file written to disk for sending the request. It has to be cleaned up -/// after the session is done. -- (void)removeTempItemAtURL:(NSURL *)fileURL { - if (!fileURL.absoluteString.length) { - return; - } - - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error = nil; - - if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) { - [_loggerDelegate - firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession013 - message:@"Failed to remove temporary uploading data file. Error" - context:error.localizedDescription]; - } -} - -/// Gets the fetcher with the session ID. -+ (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier { - NSMapTable *sessionIdentifierToFetcherMap = [self sessionIDToFetcherMap]; - FIRNetworkURLSession *session = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; - if (!session && [sessionIdentifier hasPrefix:kFIRNetworkBackgroundSessionConfigIDPrefix]) { - session = [[FIRNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil]; - [session setSessionID:sessionIdentifier]; - [sessionIdentifierToFetcherMap setObject:session forKey:sessionIdentifier]; - } - return session; -} - -/// Returns a map of the fetcher by session ID. Creates a map if it is not created. -+ (NSMapTable *)sessionIDToFetcherMap { - static NSMapTable *sessionIDToFetcherMap; - - static dispatch_once_t sessionMapOnceToken; - dispatch_once(&sessionMapOnceToken, ^{ - sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable]; - }); - return sessionIDToFetcherMap; -} - -/// Returns a map of system provided completion handler by session ID. Creates a map if it is not -/// created. -+ (FIRMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary { - static FIRMutableDictionary *systemCompletionHandlers; - - static dispatch_once_t systemCompletionHandlerOnceToken; - dispatch_once(&systemCompletionHandlerOnceToken, ^{ - systemCompletionHandlers = [[FIRMutableDictionary alloc] init]; - }); - return systemCompletionHandlers; -} - -- (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID { - NSString *tempName = [NSString stringWithFormat:@"FIRUpload_temp_%@", sessionID]; - return [_networkDirectoryURL URLByAppendingPathComponent:tempName]; -} - -/// Makes sure that the directory to store temp files exists. If not, tries to create it and returns -/// YES. If there is anything wrong, returns NO. -- (BOOL)ensureTemporaryDirectoryExists { - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSError *error = nil; - - // Create a temporary directory if it does not exist or was deleted. - if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) { - return YES; - } - - if (error && error.code != NSFileReadNoSuchFileError) { - [_loggerDelegate - firNetwork_logWithLevel:kFIRNetworkLogLevelWarning - messageCode:kFIRNetworkMessageCodeURLSession014 - message:@"Error while trying to access Network temp folder. Error" - context:error]; - } - - NSError *writeError = nil; - - [fileManager createDirectoryAtURL:_networkDirectoryURL - withIntermediateDirectories:YES - attributes:nil - error:&writeError]; - if (writeError) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession015 - message:@"Cannot create temporary directory. Error" - context:writeError]; - return NO; - } - - // Set the iCloud exclusion attribute on the Documents URL. - [self excludeFromBackupForURL:_networkDirectoryURL]; - - return YES; -} - -- (void)excludeFromBackupForURL:(NSURL *)url { - if (!url.path) { - return; - } - - // Set the iCloud exclusion attribute on the Documents URL. - NSError *preventBackupError = nil; - [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError]; - if (preventBackupError) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession016 - message:@"Cannot exclude temporary folder from iTunes backup"]; - } -} - -- (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task - willPerformHTTPRedirection:(NSHTTPURLResponse *)response - newRequest:(NSURLRequest *)request - completionHandler:(void (^)(NSURLRequest *))completionHandler { - NSArray *nonAllowedRedirectionCodes = @[ - @(kFIRNetworkHTTPStatusCodeFound), @(kFIRNetworkHTTPStatusCodeMovedPermanently), - @(kFIRNetworkHTTPStatusCodeMovedTemporarily), @(kFIRNetworkHTTPStatusCodeMultipleChoices) - ]; - - // Allow those not in the non allowed list to be followed. - if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) { - completionHandler(request); - return; - } - - // Do not allow redirection if the response code is in the non-allowed list. - NSURLRequest *newRequest = request; - - if (response) { - newRequest = nil; - } - - completionHandler(newRequest); -} - -#pragma mark - Helper Methods - -- (void)callCompletionHandler:(FIRNetworkURLSessionCompletionHandler)handler - withResponse:(NSHTTPURLResponse *)response - data:(NSData *)data - error:(NSError *)error { - if (error) { - [_loggerDelegate firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeURLSession017 - message:@"Encounter network error. Code, error" - contexts:@[ @(error.code), error ]]; - } - - if (handler) { - dispatch_async(dispatch_get_main_queue(), ^{ - handler(response, data, self->_sessionID, error); - }); - } -} - -- (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig - withRequest:(NSURLRequest *)request { - sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields; - sessionConfig.timeoutIntervalForRequest = request.timeoutInterval; - sessionConfig.timeoutIntervalForResource = request.timeoutInterval; - sessionConfig.requestCachePolicy = request.cachePolicy; -} - -@end diff --git a/Firebase/Core/FIRReachabilityChecker.m b/Firebase/Core/FIRReachabilityChecker.m deleted file mode 100644 index cac87ff..0000000 --- a/Firebase/Core/FIRReachabilityChecker.m +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2017 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import "Private/FIRReachabilityChecker+Internal.h" -#import "Private/FIRReachabilityChecker.h" - -#import "Private/FIRLogger.h" -#import "Private/FIRNetwork.h" -#import "Private/FIRNetworkMessageCode.h" - -static void ReachabilityCallback(SCNetworkReachabilityRef reachability, - SCNetworkReachabilityFlags flags, - void *info); - -static const struct FIRReachabilityApi kFIRDefaultReachabilityApi = { - SCNetworkReachabilityCreateWithName, - SCNetworkReachabilitySetCallback, - SCNetworkReachabilityScheduleWithRunLoop, - SCNetworkReachabilityUnscheduleFromRunLoop, - CFRelease, -}; - -static NSString *const kFIRReachabilityUnknownStatus = @"Unknown"; -static NSString *const kFIRReachabilityConnectedStatus = @"Connected"; -static NSString *const kFIRReachabilityDisconnectedStatus = @"Disconnected"; - -@interface FIRReachabilityChecker () - -@property(nonatomic, assign) const struct FIRReachabilityApi *reachabilityApi; -@property(nonatomic, assign) FIRReachabilityStatus reachabilityStatus; -@property(nonatomic, copy) NSString *host; -@property(nonatomic, assign) SCNetworkReachabilityRef reachability; - -@end - -@implementation FIRReachabilityChecker - -@synthesize reachabilityApi = reachabilityApi_; -@synthesize reachability = reachability_; - -- (const struct FIRReachabilityApi *)reachabilityApi { - return reachabilityApi_; -} - -- (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi { - if (reachability_) { - NSString *message = - @"Cannot change reachability API while reachability is running. " - @"Call stop first."; - [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeReachabilityChecker000 - message:message]; - return; - } - reachabilityApi_ = reachabilityApi; -} - -@synthesize reachabilityStatus = reachabilityStatus_; -@synthesize host = host_; -@synthesize reachabilityDelegate = reachabilityDelegate_; -@synthesize loggerDelegate = loggerDelegate_; - -- (BOOL)isActive { - return reachability_ != nil; -} - -- (void)setReachabilityDelegate:(id)reachabilityDelegate { - if (reachabilityDelegate && - (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(FIRReachabilityDelegate)])) { - FIRLogError(kFIRLoggerCore, - [NSString stringWithFormat:@"I-NET%06ld", - (long)kFIRNetworkMessageCodeReachabilityChecker005], - @"Reachability delegate doesn't conform to Reachability protocol."); - return; - } - reachabilityDelegate_ = reachabilityDelegate; -} - -- (void)setLoggerDelegate:(id)loggerDelegate { - if (loggerDelegate && - (![(NSObject *)loggerDelegate conformsToProtocol:@protocol(FIRNetworkLoggerDelegate)])) { - FIRLogError(kFIRLoggerCore, - [NSString stringWithFormat:@"I-NET%06ld", - (long)kFIRNetworkMessageCodeReachabilityChecker006], - @"Reachability delegate doesn't conform to Logger protocol."); - return; - } - loggerDelegate_ = loggerDelegate; -} - -- (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate - loggerDelegate:(id)loggerDelegate - withHost:(NSString *)host { - self = [super init]; - - [self setLoggerDelegate:loggerDelegate]; - - if (!host || !host.length) { - [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeReachabilityChecker001 - message:@"Invalid host specified"]; - return nil; - } - if (self) { - [self setReachabilityDelegate:reachabilityDelegate]; - reachabilityApi_ = &kFIRDefaultReachabilityApi; - reachabilityStatus_ = kFIRReachabilityUnknown; - host_ = [host copy]; - reachability_ = nil; - } - return self; -} - -- (void)dealloc { - reachabilityDelegate_ = nil; - loggerDelegate_ = nil; - [self stop]; -} - -- (BOOL)start { - if (!reachability_) { - reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]); - if (!reachability_) { - return NO; - } - SCNetworkReachabilityContext context = { - 0, /* version */ - (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */ - NULL, /* retain */ - NULL, /* release */ - NULL /* copyDescription */ - }; - if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) || - !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(), - kCFRunLoopCommonModes)) { - reachabilityApi_->releaseFn(reachability_); - reachability_ = nil; - [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelError - messageCode:kFIRNetworkMessageCodeReachabilityChecker002 - message:@"Failed to start reachability handle"]; - return NO; - } - } - [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeReachabilityChecker003 - message:@"Monitoring the network status"]; - return YES; -} - -- (void)stop { - if (reachability_) { - reachabilityStatus_ = kFIRReachabilityUnknown; - reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(), - kCFRunLoopCommonModes); - reachabilityApi_->releaseFn(reachability_); - reachability_ = nil; - } -} - -- (FIRReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags { - FIRReachabilityStatus status = kFIRReachabilityNotReachable; - // If the Reachable flag is not set, we definitely don't have connectivity. - if (flags & kSCNetworkReachabilityFlagsReachable) { - // Reachable flag is set. Check further flags. - if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) { -// Connection required flag is not set, so we have connectivity. -#if TARGET_OS_IOS || TARGET_OS_TV - status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular - : kFIRReachabilityViaWifi; -#elif TARGET_OS_OSX - status = kFIRReachabilityViaWifi; -#endif - } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand | - kSCNetworkReachabilityFlagsConnectionOnTraffic)) && - !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) { -// If the connection on demand or connection on traffic flag is set, and user intervention -// is not required, we have connectivity. -#if TARGET_OS_IOS || TARGET_OS_TV - status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kFIRReachabilityViaCellular - : kFIRReachabilityViaWifi; -#elif TARGET_OS_OSX - status = kFIRReachabilityViaWifi; -#endif - } - } - return status; -} - -- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags { - FIRReachabilityStatus status = [self statusForFlags:flags]; - if (reachabilityStatus_ != status) { - NSString *reachabilityStatusString; - if (status == kFIRReachabilityUnknown) { - reachabilityStatusString = kFIRReachabilityUnknownStatus; - } else { - reachabilityStatusString = (status == kFIRReachabilityNotReachable) - ? kFIRReachabilityDisconnectedStatus - : kFIRReachabilityConnectedStatus; - } - [loggerDelegate_ firNetwork_logWithLevel:kFIRNetworkLogLevelDebug - messageCode:kFIRNetworkMessageCodeReachabilityChecker004 - message:@"Network status has changed. Code, status" - contexts:@[ @(status), reachabilityStatusString ]]; - reachabilityStatus_ = status; - [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_]; - } -} - -@end - -static void ReachabilityCallback(SCNetworkReachabilityRef reachability, - SCNetworkReachabilityFlags flags, - void *info) { - FIRReachabilityChecker *checker = (__bridge FIRReachabilityChecker *)info; - [checker reachabilityFlagsChanged:flags]; -} - -// This function used to be at the top of the file, but it was moved here -// as a workaround for a suspected compiler bug. When compiled in Release mode -// and run on an iOS device with WiFi disabled, the reachability code crashed -// when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter. -// After unsuccessfully trying to diagnose the cause of the crash, it was -// discovered that moving this function to the end of the file magically fixed -// the crash. If you are going to edit this file, exercise caution and make sure -// to test thoroughly with an iOS device under various network conditions. -const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status) { - switch (status) { - case kFIRReachabilityUnknown: - return @"Reachability Unknown"; - - case kFIRReachabilityNotReachable: - return @"Not reachable"; - - case kFIRReachabilityViaWifi: - return @"Reachable via Wifi"; - - case kFIRReachabilityViaCellular: - return @"Reachable via Cellular Data"; - - default: - return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status]; - } -} diff --git a/Firebase/Core/FIRVersion.m b/Firebase/Core/FIRVersion.m index 97cd1bc..ec0f6ba 100644 --- a/Firebase/Core/FIRVersion.m +++ b/Firebase/Core/FIRVersion.m @@ -29,5 +29,5 @@ #define STR(x) STR_EXPAND(x) #define STR_EXPAND(x) #x -const unsigned char *const FIRVersionString = (const unsigned char *const)STR(Firebase_VERSION); -const unsigned char *const FIRCoreVersionString = (const unsigned char *const)STR(FIRCore_VERSION); +const char *const FIRVersionString = (const char *const)STR(Firebase_VERSION); +const char *const FIRCoreVersionString = (const char *const)STR(FIRCore_VERSION); diff --git a/Firebase/Core/Private/FIRMutableDictionary.h b/Firebase/Core/Private/FIRMutableDictionary.h deleted file mode 100644 index 6829dbc..0000000 --- a/Firebase/Core/Private/FIRMutableDictionary.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -/// A mutable dictionary that provides atomic accessor and mutators. -@interface FIRMutableDictionary : NSObject - -/// Returns an object given a key in the dictionary or nil if not found. -- (id)objectForKey:(id)key; - -/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. -- (void)setObject:(id)object forKey:(id)key; - -/// Removes the object given its session ID from the dictionary. -- (void)removeObjectForKey:(id)key; - -/// Removes all objects. -- (void)removeAllObjects; - -/// Returns the number of current objects in the dictionary. -- (NSUInteger)count; - -/// Returns an object given a key in the dictionary or nil if not found. -- (id)objectForKeyedSubscript:(id)key; - -/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. -- (void)setObject:(id)obj forKeyedSubscript:(id)key; - -/// Returns the immutable dictionary. -- (NSDictionary *)dictionary; - -@end diff --git a/Firebase/Core/Private/FIRNetwork.h b/Firebase/Core/Private/FIRNetwork.h deleted file mode 100644 index 32be35a..0000000 --- a/Firebase/Core/Private/FIRNetwork.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FIRNetworkConstants.h" -#import "FIRNetworkLoggerProtocol.h" -#import "FIRNetworkURLSession.h" - -/// Delegate protocol for FIRNetwork events. -@protocol FIRNetworkReachabilityDelegate - -/// Tells the delegate to handle events when the network reachability changes to connected or not -/// connected. -- (void)reachabilityDidChange; - -@end - -/// The Network component that provides network status and handles network requests and responses. -/// This is not thread safe. -/// -/// NOTE: -/// User must add FIRAnalytics handleEventsForBackgroundURLSessionID:completionHandler to the -/// AppDelegate application:handleEventsForBackgroundURLSession:completionHandler: -@interface FIRNetwork : NSObject - -/// Indicates if network connectivity is available. -@property(nonatomic, readonly, getter=isNetworkConnected) BOOL networkConnected; - -/// Indicates if there are any uploads in progress. -@property(nonatomic, readonly, getter=hasUploadInProgress) BOOL uploadInProgress; - -/// An optional delegate that can be used in the event when network reachability changes. -@property(nonatomic, weak) id reachabilityDelegate; - -/// An optional delegate that can be used to log messages, warnings or errors that occur in the -/// network operations. -@property(nonatomic, weak) id loggerDelegate; - -/// Indicates whether the logger should display debug messages. -@property(nonatomic, assign) BOOL isDebugModeEnabled; - -/// The time interval in seconds for the network request to timeout. -@property(nonatomic, assign) NSTimeInterval timeoutInterval; - -/// Initializes with the default reachability host. -- (instancetype)init; - -/// Initializes with a custom reachability host. -- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost; - -/// Handles events when background session with the given ID has finished. -+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID - completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler; - -/// Compresses and sends a POST request with the provided data to the URL. The session will be -/// background session if usingBackgroundSession is YES. Otherwise, the POST session is default -/// session. Returns a session ID or nil if an error occurs. -- (NSString *)postURL:(NSURL *)url - payload:(NSData *)payload - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(FIRNetworkCompletionHandler)handler; - -/// Sends a GET request with the provided data to the URL. The session will be background session -/// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a -/// session ID or nil if an error occurs. -- (NSString *)getURL:(NSURL *)url - headers:(NSDictionary *)headers - queue:(dispatch_queue_t)queue - usingBackgroundSession:(BOOL)usingBackgroundSession - completionHandler:(FIRNetworkCompletionHandler)handler; - -@end diff --git a/Firebase/Core/Private/FIRNetworkConstants.h b/Firebase/Core/Private/FIRNetworkConstants.h deleted file mode 100644 index d318581..0000000 --- a/Firebase/Core/Private/FIRNetworkConstants.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -/// Error codes in Firebase Network error domain. -/// Note: these error codes should never change. It would make it harder to decode the errors if -/// we inadvertently altered any of these codes in a future SDK version. -typedef NS_ENUM(NSInteger, FIRNetworkErrorCode) { - /// Unknown error. - FIRNetworkErrorCodeUnknown = 0, - /// Error occurs when the request URL is invalid. - FIRErrorCodeNetworkInvalidURL = 1, - /// Error occurs when request cannot be constructed. - FIRErrorCodeNetworkRequestCreation = 2, - /// Error occurs when payload cannot be compressed. - FIRErrorCodeNetworkPayloadCompression = 3, - /// Error occurs when session task cannot be created. - FIRErrorCodeNetworkSessionTaskCreation = 4, - /// Error occurs when there is no response. - FIRErrorCodeNetworkInvalidResponse = 5 -}; - -#pragma mark - Network constants - -/// The prefix of the ID of the background session. -extern NSString *const kFIRNetworkBackgroundSessionConfigIDPrefix; - -/// The sub directory to store the files of data that is being uploaded in the background. -extern NSString *const kFIRNetworkApplicationSupportSubdirectory; - -/// Name of the temporary directory that stores files for background uploading. -extern NSString *const kFIRNetworkTempDirectoryName; - -/// The period when the temporary uploading file can stay. -extern const NSTimeInterval kFIRNetworkTempFolderExpireTime; - -/// The default network request timeout interval. -extern const NSTimeInterval kFIRNetworkTimeOutInterval; - -/// The host to check the reachability of the network. -extern NSString *const kFIRNetworkReachabilityHost; - -/// The key to get the error context of the UserInfo. -extern NSString *const kFIRNetworkErrorContext; - -#pragma mark - Network Status Code - -extern const int kFIRNetworkHTTPStatusOK; -extern const int kFIRNetworkHTTPStatusNoContent; -extern const int kFIRNetworkHTTPStatusCodeMultipleChoices; -extern const int kFIRNetworkHTTPStatusCodeMovedPermanently; -extern const int kFIRNetworkHTTPStatusCodeFound; -extern const int kFIRNetworkHTTPStatusCodeNotModified; -extern const int kFIRNetworkHTTPStatusCodeMovedTemporarily; -extern const int kFIRNetworkHTTPStatusCodeNotFound; -extern const int kFIRNetworkHTTPStatusCodeCannotAcceptTraffic; -extern const int kFIRNetworkHTTPStatusCodeUnavailable; - -#pragma mark - Error Domain - -extern NSString *const kFIRNetworkErrorDomain; diff --git a/Firebase/Core/Private/FIRNetworkLoggerProtocol.h b/Firebase/Core/Private/FIRNetworkLoggerProtocol.h deleted file mode 100644 index add70fc..0000000 --- a/Firebase/Core/Private/FIRNetworkLoggerProtocol.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FIRLoggerLevel.h" -#import "FIRNetworkMessageCode.h" - -/// The log levels used by FIRNetworkLogger. -typedef NS_ENUM(NSInteger, FIRNetworkLogLevel) { - kFIRNetworkLogLevelError = FIRLoggerLevelError, - kFIRNetworkLogLevelWarning = FIRLoggerLevelWarning, - kFIRNetworkLogLevelInfo = FIRLoggerLevelInfo, - kFIRNetworkLogLevelDebug = FIRLoggerLevelDebug, -}; - -@protocol FIRNetworkLoggerDelegate - -@required -/// Tells the delegate to log a message with an array of contexts and the log level. -- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel - messageCode:(FIRNetworkMessageCode)messageCode - message:(NSString *)message - contexts:(NSArray *)contexts; - -/// Tells the delegate to log a message with a context and the log level. -- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel - messageCode:(FIRNetworkMessageCode)messageCode - message:(NSString *)message - context:(id)context; - -/// Tells the delegate to log a message with the log level. -- (void)firNetwork_logWithLevel:(FIRNetworkLogLevel)logLevel - messageCode:(FIRNetworkMessageCode)messageCode - message:(NSString *)message; - -@end diff --git a/Firebase/Core/Private/FIRNetworkMessageCode.h b/Firebase/Core/Private/FIRNetworkMessageCode.h deleted file mode 100644 index 30f562f..0000000 --- a/Firebase/Core/Private/FIRNetworkMessageCode.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. -typedef NS_ENUM(NSInteger, FIRNetworkMessageCode) { - // FIRNetwork.m - kFIRNetworkMessageCodeNetwork000 = 900000, // I-NET900000 - kFIRNetworkMessageCodeNetwork001 = 900001, // I-NET900001 - kFIRNetworkMessageCodeNetwork002 = 900002, // I-NET900002 - kFIRNetworkMessageCodeNetwork003 = 900003, // I-NET900003 - // FIRNetworkURLSession.m - kFIRNetworkMessageCodeURLSession000 = 901000, // I-NET901000 - kFIRNetworkMessageCodeURLSession001 = 901001, // I-NET901001 - kFIRNetworkMessageCodeURLSession002 = 901002, // I-NET901002 - kFIRNetworkMessageCodeURLSession003 = 901003, // I-NET901003 - kFIRNetworkMessageCodeURLSession004 = 901004, // I-NET901004 - kFIRNetworkMessageCodeURLSession005 = 901005, // I-NET901005 - kFIRNetworkMessageCodeURLSession006 = 901006, // I-NET901006 - kFIRNetworkMessageCodeURLSession007 = 901007, // I-NET901007 - kFIRNetworkMessageCodeURLSession008 = 901008, // I-NET901008 - kFIRNetworkMessageCodeURLSession009 = 901009, // I-NET901009 - kFIRNetworkMessageCodeURLSession010 = 901010, // I-NET901010 - kFIRNetworkMessageCodeURLSession011 = 901011, // I-NET901011 - kFIRNetworkMessageCodeURLSession012 = 901012, // I-NET901012 - kFIRNetworkMessageCodeURLSession013 = 901013, // I-NET901013 - kFIRNetworkMessageCodeURLSession014 = 901014, // I-NET901014 - kFIRNetworkMessageCodeURLSession015 = 901015, // I-NET901015 - kFIRNetworkMessageCodeURLSession016 = 901016, // I-NET901016 - kFIRNetworkMessageCodeURLSession017 = 901017, // I-NET901017 - kFIRNetworkMessageCodeURLSession018 = 901018, // I-NET901018 - // FIRReachabilityChecker.m - kFIRNetworkMessageCodeReachabilityChecker000 = 902000, // I-NET902000 - kFIRNetworkMessageCodeReachabilityChecker001 = 902001, // I-NET902001 - kFIRNetworkMessageCodeReachabilityChecker002 = 902002, // I-NET902002 - kFIRNetworkMessageCodeReachabilityChecker003 = 902003, // I-NET902003 - kFIRNetworkMessageCodeReachabilityChecker004 = 902004, // I-NET902004 - kFIRNetworkMessageCodeReachabilityChecker005 = 902005, // I-NET902005 - kFIRNetworkMessageCodeReachabilityChecker006 = 902006, // I-NET902006 -}; diff --git a/Firebase/Core/Private/FIRNetworkURLSession.h b/Firebase/Core/Private/FIRNetworkURLSession.h deleted file mode 100644 index a51b8a9..0000000 --- a/Firebase/Core/Private/FIRNetworkURLSession.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -#import "FIRNetworkLoggerProtocol.h" - -typedef void (^FIRNetworkCompletionHandler)(NSHTTPURLResponse *response, - NSData *data, - NSError *error); -typedef void (^FIRNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *response, - NSData *data, - NSString *sessionID, - NSError *error); -typedef void (^FIRNetworkSystemCompletionHandler)(void); - -/// The protocol that uses NSURLSession for iOS >= 7.0 to handle requests and responses. -@interface FIRNetworkURLSession - : NSObject - -/// Indicates whether the background network is enabled. Default value is NO. -@property(nonatomic, getter=isBackgroundNetworkEnabled) BOOL backgroundNetworkEnabled; - -/// The logger delegate to log message, errors or warnings that occur during the network operations. -@property(nonatomic, weak) id loggerDelegate; - -/// Calls the system provided completion handler after the background session is finished. -+ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID - completionHandler:(FIRNetworkSystemCompletionHandler)completionHandler; - -/// Initializes with logger delegate. -- (instancetype)initWithNetworkLoggerDelegate:(id)networkLoggerDelegate - NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; - -/// Sends an asynchronous POST request and calls the provided completion handler when the request -/// completes or when errors occur, and returns an ID of the session/connection. -- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request - completionHandler:(FIRNetworkURLSessionCompletionHandler)handler; - -/// Sends an asynchronous GET request and calls the provided completion handler when the request -/// completes or when errors occur, and returns an ID of the session. -- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request - completionHandler:(FIRNetworkURLSessionCompletionHandler)handler; - -@end diff --git a/Firebase/Core/Private/FIRReachabilityChecker+Internal.h b/Firebase/Core/Private/FIRReachabilityChecker+Internal.h deleted file mode 100644 index f82d103..0000000 --- a/Firebase/Core/Private/FIRReachabilityChecker+Internal.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import "FIRReachabilityChecker.h" - -typedef SCNetworkReachabilityRef (*FIRReachabilityCreateWithNameFn)(CFAllocatorRef allocator, - const char *host); - -typedef Boolean (*FIRReachabilitySetCallbackFn)(SCNetworkReachabilityRef target, - SCNetworkReachabilityCallBack callback, - SCNetworkReachabilityContext *context); -typedef Boolean (*FIRReachabilityScheduleWithRunLoopFn)(SCNetworkReachabilityRef target, - CFRunLoopRef runLoop, - CFStringRef runLoopMode); -typedef Boolean (*FIRReachabilityUnscheduleFromRunLoopFn)(SCNetworkReachabilityRef target, - CFRunLoopRef runLoop, - CFStringRef runLoopMode); - -typedef void (*FIRReachabilityReleaseFn)(CFTypeRef cf); - -struct FIRReachabilityApi { - FIRReachabilityCreateWithNameFn createWithNameFn; - FIRReachabilitySetCallbackFn setCallbackFn; - FIRReachabilityScheduleWithRunLoopFn scheduleWithRunLoopFn; - FIRReachabilityUnscheduleFromRunLoopFn unscheduleFromRunLoopFn; - FIRReachabilityReleaseFn releaseFn; -}; - -@interface FIRReachabilityChecker (Internal) - -- (const struct FIRReachabilityApi *)reachabilityApi; -- (void)setReachabilityApi:(const struct FIRReachabilityApi *)reachabilityApi; - -@end diff --git a/Firebase/Core/Private/FIRReachabilityChecker.h b/Firebase/Core/Private/FIRReachabilityChecker.h deleted file mode 100644 index 3a6a531..0000000 --- a/Firebase/Core/Private/FIRReachabilityChecker.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import -#import - -/// Reachability Status -typedef enum { - kFIRReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable. - kFIRReachabilityNotReachable, ///< Host is not reachable. - kFIRReachabilityViaWifi, ///< Host is reachable via Wifi. - kFIRReachabilityViaCellular, ///< Host is reachable via cellular. -} FIRReachabilityStatus; - -const NSString *FIRReachabilityStatusString(FIRReachabilityStatus status); - -@class FIRReachabilityChecker; -@protocol FIRNetworkLoggerDelegate; - -/// Google Analytics iOS Reachability Checker. -@protocol FIRReachabilityDelegate -@required -/// Called when network status has changed. -- (void)reachability:(FIRReachabilityChecker *)reachability - statusChanged:(FIRReachabilityStatus)status; -@end - -/// Google Analytics iOS Network Status Checker. -@interface FIRReachabilityChecker : NSObject - -/// The last known reachability status, or FIRReachabilityStatusUnknown if the -/// checker is not active. -@property(nonatomic, readonly) FIRReachabilityStatus reachabilityStatus; -/// The host to which reachability status is to be checked. -@property(nonatomic, copy, readonly) NSString *host; -/// The delegate to be notified of reachability status changes. -@property(nonatomic, weak) id reachabilityDelegate; -/// The delegate to be notified to log messages. -@property(nonatomic, weak) id loggerDelegate; -/// `YES` if the reachability checker is active, `NO` otherwise. -@property(nonatomic, readonly) BOOL isActive; - -/// Initialize the reachability checker. Note that you must call start to begin checking for and -/// receiving notifications about network status changes. -/// -/// @param reachabilityDelegate The delegate to be notified when reachability status to host -/// changes. -/// -/// @param loggerDelegate The delegate to send log messages to. -/// -/// @param host The name of the host. -/// -- (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate - loggerDelegate:(id)loggerDelegate - withHost:(NSString *)host; - -- (instancetype)init NS_UNAVAILABLE; - -/// Start checking for reachability to the specified host. This has no effect if the status -/// checker is already checking for connectivity. -/// -/// @return `YES` if initiating status checking was successful or the status checking has already -/// been initiated, `NO` otherwise. -- (BOOL)start; - -/// Stop checking for reachability to the specified host. This has no effect if the status -/// checker is not checking for connectivity. -- (void)stop; - -@end diff --git a/Firebase/Core/Private/FIRVersion.h b/Firebase/Core/Private/FIRVersion.h index cec0994..226efb1 100644 --- a/Firebase/Core/Private/FIRVersion.h +++ b/Firebase/Core/Private/FIRVersion.h @@ -17,7 +17,7 @@ #import /** The version of the Firebase SDK. */ -FOUNDATION_EXPORT const unsigned char *const FIRVersionString; +FOUNDATION_EXPORT const char *const FIRVersionString; /** The version of the FirebaseCore Component. */ -FOUNDATION_EXPORT const unsigned char *const FIRCoreVersionString; +FOUNDATION_EXPORT const char *const FIRCoreVersionString; diff --git a/Firebase/Core/Public/FIRLoggerLevel.h b/Firebase/Core/Public/FIRLoggerLevel.h index 8b6579f..e17e338 100644 --- a/Firebase/Core/Public/FIRLoggerLevel.h +++ b/Firebase/Core/Public/FIRLoggerLevel.h @@ -14,6 +14,9 @@ * limitations under the License. */ +// Note that importing GULLoggerLevel.h will lead to a non-modular header +// import error. + /** * The log levels used by internal logging. */ diff --git a/Firebase/Core/third_party/FIRAppEnvironmentUtil.h b/Firebase/Core/third_party/FIRAppEnvironmentUtil.h deleted file mode 100644 index 09ee504..0000000 --- a/Firebase/Core/third_party/FIRAppEnvironmentUtil.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2017 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#import - -@interface FIRAppEnvironmentUtil : NSObject - -/// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator, -/// development environment or sideloaded. -+ (BOOL)isFromAppStore; - -/// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt. -/// Returns NO otherwise. -+ (BOOL)isAppStoreReceiptSandbox; - -/// Indicates whether the app is on simulator or not at runtime depending on the device -/// architecture. -+ (BOOL)isSimulator; - -/// The current device model. Returns an empty string if device model cannot be retrieved. -+ (NSString *)deviceModel; - -/// The current operating system version. Returns an empty string if the system version cannot be -/// retrieved. -+ (NSString *)systemVersion; - -/// Indicates whether it is running inside an extension or an app. -+ (BOOL)isAppExtension; - -@end diff --git a/Firebase/Core/third_party/FIRAppEnvironmentUtil.m b/Firebase/Core/third_party/FIRAppEnvironmentUtil.m deleted file mode 100644 index be4e971..0000000 --- a/Firebase/Core/third_party/FIRAppEnvironmentUtil.m +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2017 Google -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#import - -#import "FIRAppEnvironmentUtil.h" - -#import -#import -#import - -/// The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from -/// the iPhoneOS or Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just -/// provide the definitions here. -#if TARGET_OS_SIMULATOR && !defined(LC_ENCRYPTION_INFO) -#define LC_ENCRYPTION_INFO 0x21 -struct encryption_info_command { - uint32_t cmd; - uint32_t cmdsize; - uint32_t cryptoff; - uint32_t cryptsize; - uint32_t cryptid; -}; -#endif - -@implementation FIRAppEnvironmentUtil - -/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox. -/// This will affect your data integrity when using Firebase Analytics, as it will disable some -/// necessary checks. -static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey = - @"FirebaseAppStoreReceiptURLCheckEnabled"; - -/// The file name of the sandbox receipt. This is available on iOS >= 8.0 -static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt"; - -/// The following copyright from Landon J. Fuller applies to the isAppEncrypted function. -/// -/// Copyright (c) 2017 Landon J. Fuller -/// All rights reserved. -/// -/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software -/// and associated documentation files (the "Software"), to deal in the Software without -/// restriction, including without limitation the rights to use, copy, modify, merge, publish, -/// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the -/// Software is furnished to do so, subject to the following conditions: -/// -/// The above copyright notice and this permission notice shall be included in all copies or -/// substantial portions of the Software. -/// -/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -/// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -/// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -/// -/// Comment from iPhone Dev Wiki -/// Crack Prevention: -/// App Store binaries are signed by both their developer and Apple. This encrypts the binary so -/// that decryption keys are needed in order to make the binary readable. When iOS executes the -/// binary, the decryption keys are used to decrypt the binary into a readable state where it is -/// then loaded into memory and executed. iOS can tell the encryption status of a binary via the -/// cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero -/// value then the binary is encrypted. -/// -/// 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into -/// a new binary file, resigning, and repackaging. This will only work on jailbroken devices as -/// codesignature validation has been removed. Resigning takes place because while the codesignature -/// doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have -/// AppSync or similar to disable codesignature checks. -/// -/// More information at Landon Fuller's blog -static BOOL isAppEncrypted() { - const struct mach_header *executableHeader = NULL; - for (uint32_t i = 0; i < _dyld_image_count(); i++) { - const struct mach_header *header = _dyld_get_image_header(i); - if (header && header->filetype == MH_EXECUTE) { - executableHeader = header; - break; - } - } - - if (!executableHeader) { - return NO; - } - - BOOL is64bit = (executableHeader->magic == MH_MAGIC_64); - uintptr_t cursor = (uintptr_t)executableHeader + - (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); - const struct segment_command *segmentCommand = NULL; - uint32_t i = 0; - - while (i++ < executableHeader->ncmds) { - segmentCommand = (struct segment_command *)cursor; - - if (!segmentCommand) { - continue; - } - - if ((!is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO) || - (is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO_64)) { - if (is64bit) { - struct encryption_info_command_64 *cryptCmd = - (struct encryption_info_command_64 *)segmentCommand; - return cryptCmd && cryptCmd->cryptid != 0; - } else { - struct encryption_info_command *cryptCmd = (struct encryption_info_command *)segmentCommand; - return cryptCmd && cryptCmd->cryptid != 0; - } - } - cursor += segmentCommand->cmdsize; - } - - return NO; -} - -+ (BOOL)isFromAppStore { - static dispatch_once_t isEncryptedOnce; - static BOOL isEncrypted = NO; - - dispatch_once(&isEncryptedOnce, ^{ - isEncrypted = isAppEncrypted(); - }); - - if ([FIRAppEnvironmentUtil isSimulator]) { - return NO; - } - - // If an app contain the sandboxReceipt file, it means its coming from TestFlight - // This must be checked before the SCInfo Folder check below since TestFlight apps may - // also have an SCInfo folder. - if ([FIRAppEnvironmentUtil isAppStoreReceiptSandbox]) { - return NO; - } - - if ([FIRAppEnvironmentUtil hasSCInfoFolder]) { - // When iTunes downloads a .ipa, it also gets a customized .sinf file which is added to the - // main SC_Info directory. - return YES; - } - - // For iOS >= 8.0, iTunesMetadata.plist is moved outside of the sandbox. Any attempt to read - // the iTunesMetadata.plist outside of the sandbox will be rejected by Apple. - // If the app does not contain the embedded.mobileprovision which is stripped out by Apple when - // the app is submitted to store, then it is highly likely that it is from Apple Store. - return isEncrypted && ![FIRAppEnvironmentUtil hasEmbeddedMobileProvision]; -} - -+ (BOOL)isAppStoreReceiptSandbox { - // Since checking the App Store's receipt URL can be memory intensive, check the option in the - // Info.plist if developers opted out of this check. - id enableSandboxCheck = - [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey]; - if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] && - ![enableSandboxCheck boolValue]) { - return NO; - } - - NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL; - NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent; - return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName]; -} - -+ (BOOL)hasEmbeddedMobileProvision { -#if TARGET_OS_IOS || TARGET_OS_TV - return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0; -#elif TARGET_OS_OSX - return NO; -#endif -} - -+ (BOOL)isSimulator { -#if TARGET_OS_IOS || TARGET_OS_TV - NSString *platform = [FIRAppEnvironmentUtil deviceModel]; - return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"]; -#elif TARGET_OS_OSX - return NO; -#endif -} - -+ (NSString *)deviceModel { - static dispatch_once_t once; - static NSString *deviceModel; - - dispatch_once(&once, ^{ - struct utsname systemInfo; - if (uname(&systemInfo) == 0) { - deviceModel = [NSString stringWithUTF8String:systemInfo.machine]; - } - }); - return deviceModel; -} - -+ (NSString *)systemVersion { - // Assemble the systemVersion, excluding the patch version if it's 0. - NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; - NSMutableString *versionString = [[NSMutableString alloc] - initWithFormat:@"%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion]; - if (osVersion.patchVersion != 0) { - [versionString appendFormat:@".%ld", (long)osVersion.patchVersion]; - } - - return versionString; -} - -+ (BOOL)isAppExtension { -#if TARGET_OS_IOS || TARGET_OS_TV - // Documented by Apple - BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; - return appExtension; -#elif TARGET_OS_OSX - return NO; -#endif -} - -#pragma mark - Helper methods - -+ (BOOL)hasSCInfoFolder { -#if TARGET_OS_IOS || TARGET_OS_TV - NSString *bundlePath = [NSBundle mainBundle].bundlePath; - NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"]; - return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath]; -#elif TARGET_OS_OSX - return NO; -#endif -} - -@end diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m index 1152032..5dd7004 100644 --- a/Firebase/Messaging/FIRMessaging.m +++ b/Firebase/Messaging/FIRMessaging.m @@ -37,8 +37,8 @@ #import "FIRMessagingVersionUtilities.h" #import -#import #import +#import #import "NSError+FIRMessaging.h" @@ -125,7 +125,7 @@ NSString *const kFIRMessagingPlistAutoInitEnabled = @end @interface FIRMessaging () + GULReachabilityDelegate> // FIRApp properties @property(nonatomic, readwrite, strong) NSData *apnsTokenData; @@ -136,7 +136,7 @@ NSString *const kFIRMessagingPlistAutoInitEnabled = @property(nonatomic, readwrite, assign) BOOL isClientSetup; @property(nonatomic, readwrite, strong) FIRMessagingClient *client; -@property(nonatomic, readwrite, strong) FIRReachabilityChecker *reachability; +@property(nonatomic, readwrite, strong) GULReachabilityChecker *reachability; @property(nonatomic, readwrite, strong) FIRMessagingDataMessageManager *dataMessageManager; @property(nonatomic, readwrite, strong) FIRMessagingPubSub *pubsub; @property(nonatomic, readwrite, strong) FIRMessagingRmqManager *rmq2Manager; @@ -200,8 +200,7 @@ NSString *const kFIRMessagingPlistAutoInitEnabled = [self setupReceiver]; NSString *hostname = kFIRMessagingReachabilityHostname; - self.reachability = [[FIRReachabilityChecker alloc] initWithReachabilityDelegate:self - loggerDelegate:nil + self.reachability = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self withHost:hostname]; [self.reachability start]; @@ -800,10 +799,10 @@ NSString *const kFIRMessagingPlistAutoInitEnabled = } } -#pragma mark - FIRReachabilityDelegate +#pragma mark - GULReachabilityDelegate -- (void)reachability:(FIRReachabilityChecker *)reachability - statusChanged:(FIRReachabilityStatus)status { +- (void)reachability:(GULReachabilityChecker *)reachability + statusChanged:(GULReachabilityStatus)status { [self onNetworkStatusChanged]; } @@ -821,15 +820,15 @@ NSString *const kFIRMessagingPlistAutoInitEnabled = } - (BOOL)isNetworkAvailable { - FIRReachabilityStatus status = self.reachability.reachabilityStatus; - return (status == kFIRReachabilityViaCellular || status == kFIRReachabilityViaWifi); + GULReachabilityStatus status = self.reachability.reachabilityStatus; + return (status == kGULReachabilityViaCellular || status == kGULReachabilityViaWifi); } - (FIRMessagingNetworkStatus)networkType { - FIRReachabilityStatus status = self.reachability.reachabilityStatus; + GULReachabilityStatus status = self.reachability.reachabilityStatus; if (![self isNetworkAvailable]) { return kFIRMessagingReachabilityNotReachable; - } else if (status == kFIRReachabilityViaCellular) { + } else if (status == kGULReachabilityViaCellular) { return kFIRMessagingReachabilityReachableViaWWAN; } else { return kFIRMessagingReachabilityReachableViaWiFi; diff --git a/Firebase/Messaging/FIRMessagingClient.h b/Firebase/Messaging/FIRMessagingClient.h index cb76e98..98337a3 100644 --- a/Firebase/Messaging/FIRMessagingClient.h +++ b/Firebase/Messaging/FIRMessagingClient.h @@ -16,7 +16,7 @@ #import "FIRMessaging.h" -@class FIRReachabilityChecker; +@class GULReachabilityChecker; @class GPBMessage; @class FIRMessagingConnection; @@ -48,7 +48,7 @@ typedef void(^FIRMessagingConnectCompletionHandler)(NSError *error); // Designated initializer - (instancetype)initWithDelegate:(id)delegate - reachability:(FIRReachabilityChecker *)reachability + reachability:(GULReachabilityChecker *)reachability rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager; - (void)teardown; diff --git a/Firebase/Messaging/FIRMessagingClient.m b/Firebase/Messaging/FIRMessagingClient.m index 0bfb5a8..9d8c558 100644 --- a/Firebase/Messaging/FIRMessagingClient.m +++ b/Firebase/Messaging/FIRMessagingClient.m @@ -16,7 +16,7 @@ #import "FIRMessagingClient.h" -#import +#import #import "FIRMessaging.h" #import "FIRMessagingConnection.h" @@ -82,7 +82,7 @@ static NSUInteger FIRMessagingServerPort() { // FIRMessagingService owns these instances @property(nonatomic, readwrite, weak) FIRMessagingRmqManager *rmq2Manager; -@property(nonatomic, readwrite, weak) FIRReachabilityChecker *reachability; +@property(nonatomic, readwrite, weak) GULReachabilityChecker *reachability; @property(nonatomic, readwrite, assign) int64_t lastConnectedTimestamp; @property(nonatomic, readwrite, assign) int64_t lastDisconnectedTimestamp; @@ -111,7 +111,7 @@ static NSUInteger FIRMessagingServerPort() { } - (instancetype)initWithDelegate:(id)delegate - reachability:(FIRReachabilityChecker *)reachability + reachability:(GULReachabilityChecker *)reachability rmq2Manager:(FIRMessagingRmqManager *)rmq2Manager { self = [super init]; if (self) { @@ -471,8 +471,8 @@ static NSUInteger FIRMessagingServerPort() { #pragma mark - Schedulers - (void)scheduleConnectRetry { - FIRReachabilityStatus status = self.reachability.reachabilityStatus; - BOOL isReachable = (status == kFIRReachabilityViaWifi || status == kFIRReachabilityViaCellular); + GULReachabilityStatus status = self.reachability.reachabilityStatus; + BOOL isReachable = (status == kGULReachabilityViaWifi || status == kGULReachabilityViaCellular); if (!isReachable) { FIRMessagingLoggerDebug(kFIRMessagingMessageCodeClient010, @"Internet not reachable when signing into MCS during a retry"); diff --git a/Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.h b/Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.h new file mode 100644 index 0000000..5b56271 --- /dev/null +++ b/Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.h @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface GULAppEnvironmentUtil : NSObject + +/// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator, +/// development environment or sideloaded. ++ (BOOL)isFromAppStore; + +/// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt. +/// Returns NO otherwise. ++ (BOOL)isAppStoreReceiptSandbox; + +/// Indicates whether the app is on simulator or not at runtime depending on the device +/// architecture. ++ (BOOL)isSimulator; + +/// The current device model. Returns an empty string if device model cannot be retrieved. ++ (NSString *)deviceModel; + +/// The current operating system version. Returns an empty string if the system version cannot be +/// retrieved. ++ (NSString *)systemVersion; + +/// Indicates whether it is running inside an extension or an app. ++ (BOOL)isAppExtension; + +@end diff --git a/Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.m b/Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.m new file mode 100644 index 0000000..e96ba70 --- /dev/null +++ b/Firebase/Utilities/Environment/third_party/GULAppEnvironmentUtil.m @@ -0,0 +1,236 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GULAppEnvironmentUtil.h" + +#import +#import +#import +#import + +/// The encryption info struct and constants are missing from the iPhoneSimulator SDK, but not from +/// the iPhoneOS or Mac OS X SDKs. Since one doesn't ever ship a Simulator binary, we'll just +/// provide the definitions here. +#if TARGET_OS_SIMULATOR && !defined(LC_ENCRYPTION_INFO) +#define LC_ENCRYPTION_INFO 0x21 +struct encryption_info_command { + uint32_t cmd; + uint32_t cmdsize; + uint32_t cryptoff; + uint32_t cryptsize; + uint32_t cryptid; +}; +#endif + +@implementation GULAppEnvironmentUtil + +/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox. +/// This will affect your data integrity when using Firebase Analytics, as it will disable some +/// necessary checks. +static NSString *const kFIRAppStoreReceiptURLCheckEnabledKey = + @"FirebaseAppStoreReceiptURLCheckEnabled"; + +/// The file name of the sandbox receipt. This is available on iOS >= 8.0 +static NSString *const kFIRAIdentitySandboxReceiptFileName = @"sandboxReceipt"; + +/// The following copyright from Landon J. Fuller applies to the isAppEncrypted function. +/// +/// Copyright (c) 2017 Landon J. Fuller +/// All rights reserved. +/// +/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software +/// and associated documentation files (the "Software"), to deal in the Software without +/// restriction, including without limitation the rights to use, copy, modify, merge, publish, +/// distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +/// Software is furnished to do so, subject to the following conditions: +/// +/// The above copyright notice and this permission notice shall be included in all copies or +/// substantial portions of the Software. +/// +/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +/// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +/// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +/// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +/// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +/// +/// Comment from iPhone Dev Wiki +/// Crack Prevention: +/// App Store binaries are signed by both their developer and Apple. This encrypts the binary so +/// that decryption keys are needed in order to make the binary readable. When iOS executes the +/// binary, the decryption keys are used to decrypt the binary into a readable state where it is +/// then loaded into memory and executed. iOS can tell the encryption status of a binary via the +/// cryptid structure member of LC_ENCRYPTION_INFO MachO load command. If cryptid is a non-zero +/// value then the binary is encrypted. +/// +/// 'Cracking' works by letting the kernel decrypt the binary then siphoning the decrypted data into +/// a new binary file, resigning, and repackaging. This will only work on jailbroken devices as +/// codesignature validation has been removed. Resigning takes place because while the codesignature +/// doesn't have to be valid thanks to the jailbreak, it does have to be in place unless you have +/// AppSync or similar to disable codesignature checks. +/// +/// More information at Landon Fuller's blog +static BOOL IsAppEncrypted() { + const struct mach_header *executableHeader = NULL; + for (uint32_t i = 0; i < _dyld_image_count(); i++) { + const struct mach_header *header = _dyld_get_image_header(i); + if (header && header->filetype == MH_EXECUTE) { + executableHeader = header; + break; + } + } + + if (!executableHeader) { + return NO; + } + + BOOL is64bit = (executableHeader->magic == MH_MAGIC_64); + uintptr_t cursor = (uintptr_t)executableHeader + + (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); + const struct segment_command *segmentCommand = NULL; + uint32_t i = 0; + + while (i++ < executableHeader->ncmds) { + segmentCommand = (struct segment_command *)cursor; + + if (!segmentCommand) { + continue; + } + + if ((!is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO) || + (is64bit && segmentCommand->cmd == LC_ENCRYPTION_INFO_64)) { + if (is64bit) { + struct encryption_info_command_64 *cryptCmd = + (struct encryption_info_command_64 *)segmentCommand; + return cryptCmd && cryptCmd->cryptid != 0; + } else { + struct encryption_info_command *cryptCmd = (struct encryption_info_command *)segmentCommand; + return cryptCmd && cryptCmd->cryptid != 0; + } + } + cursor += segmentCommand->cmdsize; + } + + return NO; +} + +static BOOL HasSCInfoFolder() { +#if TARGET_OS_IOS || TARGET_OS_TV + NSString *bundlePath = [NSBundle mainBundle].bundlePath; + NSString *scInfoPath = [bundlePath stringByAppendingPathComponent:@"SC_Info"]; + return [[NSFileManager defaultManager] fileExistsAtPath:scInfoPath]; +#elif TARGET_OS_OSX + return NO; +#endif +} + +static BOOL HasEmbeddedMobileProvision() { +#if TARGET_OS_IOS || TARGET_OS_TV + return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0; +#elif TARGET_OS_OSX + return NO; +#endif +} + ++ (BOOL)isFromAppStore { + static dispatch_once_t isEncryptedOnce; + static BOOL isEncrypted = NO; + + dispatch_once(&isEncryptedOnce, ^{ + isEncrypted = IsAppEncrypted(); + }); + + if ([GULAppEnvironmentUtil isSimulator]) { + return NO; + } + + // If an app contain the sandboxReceipt file, it means its coming from TestFlight + // This must be checked before the SCInfo Folder check below since TestFlight apps may + // also have an SCInfo folder. + if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox]) { + return NO; + } + + if (HasSCInfoFolder()) { + // When iTunes downloads a .ipa, it also gets a customized .sinf file which is added to the + // main SC_Info directory. + return YES; + } + + // For iOS >= 8.0, iTunesMetadata.plist is moved outside of the sandbox. Any attempt to read + // the iTunesMetadata.plist outside of the sandbox will be rejected by Apple. + // If the app does not contain the embedded.mobileprovision which is stripped out by Apple when + // the app is submitted to store, then it is highly likely that it is from Apple Store. + return isEncrypted && !HasEmbeddedMobileProvision(); +} + ++ (BOOL)isAppStoreReceiptSandbox { + // Since checking the App Store's receipt URL can be memory intensive, check the option in the + // Info.plist if developers opted out of this check. + id enableSandboxCheck = + [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey]; + if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] && + ![enableSandboxCheck boolValue]) { + return NO; + } + + NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL; + NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent; + return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName]; +} + ++ (BOOL)isSimulator { +#if TARGET_OS_IOS || TARGET_OS_TV + NSString *platform = [GULAppEnvironmentUtil deviceModel]; + return [platform isEqual:@"x86_64"] || [platform isEqual:@"i386"]; +#elif TARGET_OS_OSX + return NO; +#endif +} + ++ (NSString *)deviceModel { + static dispatch_once_t once; + static NSString *deviceModel; + + dispatch_once(&once, ^{ + struct utsname systemInfo; + if (uname(&systemInfo) == 0) { + deviceModel = [NSString stringWithUTF8String:systemInfo.machine]; + } + }); + return deviceModel; +} + ++ (NSString *)systemVersion { + // Assemble the systemVersion, excluding the patch version if it's 0. + NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion; + NSMutableString *versionString = [[NSMutableString alloc] + initWithFormat:@"%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion]; + if (osVersion.patchVersion != 0) { + [versionString appendFormat:@".%ld", (long)osVersion.patchVersion]; + } + + return versionString; +} + ++ (BOOL)isAppExtension { +#if TARGET_OS_IOS || TARGET_OS_TV + // Documented by Apple + BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]; + return appExtension; +#elif TARGET_OS_OSX + return NO; +#endif +} + +@end diff --git a/Firebase/Utilities/Logger/GULLogger.m b/Firebase/Utilities/Logger/GULLogger.m new file mode 100644 index 0000000..5c808ea --- /dev/null +++ b/Firebase/Utilities/Logger/GULLogger.m @@ -0,0 +1,214 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Private/GULLogger.h" + +#include + +#import +#import "Public/GULLoggerLevel.h" + +/// Key for the debug mode bit in NSUserDefaults. +NSString *const kGULPersistedDebugModeKey = @"/google/utilities/debug_mode"; + +/// ASL client facility name used by GULLogger. +const char *kGULLoggerASLClientFacilityName = "com.google.utilities.logger"; + +static dispatch_once_t sGULLoggerOnceToken; + +static aslclient sGULLoggerClient; + +static dispatch_queue_t sGULClientQueue; + +static BOOL sGULLoggerDebugMode; + +static GULLoggerLevel sGULLoggerMaximumLevel; + +// Allow clients to register a version to include in the log. +static const char *sVersion = ""; + +static GULLoggerService kGULLoggerLogger = @"[GULLogger]"; + +#ifdef DEBUG +/// The regex pattern for the message code. +static NSString *const kMessageCodePattern = @"^I-[A-Z]{3}[0-9]{6}$"; +static NSRegularExpression *sMessageCodeRegex; +#endif + +void GULLoggerInitializeASL(BOOL overrideSTDERR, BOOL forceDebugMode) { + dispatch_once(&sGULLoggerOnceToken, ^{ + NSInteger majorOSVersion = [[GULAppEnvironmentUtil systemVersion] integerValue]; + uint32_t aslOptions = ASL_OPT_STDERR; +#if TARGET_OS_SIMULATOR + // The iOS 11 simulator doesn't need the ASL_OPT_STDERR flag. + if (majorOSVersion >= 11) { + aslOptions = 0; + } +#else + // Devices running iOS 10 or higher don't need the ASL_OPT_STDERR flag. + if (majorOSVersion >= 10) { + aslOptions = 0; + } +#endif // TARGET_OS_SIMULATOR + + // Override the aslOptions to ASL_OPT_STDERR if the override argument is passed in. + if (overrideSTDERR) { + aslOptions = ASL_OPT_STDERR; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated + // Initialize the ASL client handle. + sGULLoggerClient = asl_open(NULL, kGULLoggerASLClientFacilityName, aslOptions); + sGULLoggerDebugMode = forceDebugMode; + sGULLoggerMaximumLevel = GULLoggerLevelNotice; + + if (forceDebugMode) { + asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG)); + } else { + // Set the filter used by system/device log. Initialize in default mode. + asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE)); + } + + // We should disable debug mode if we are running from App Store. + if (sGULLoggerDebugMode && [GULAppEnvironmentUtil isFromAppStore]) { + sGULLoggerDebugMode = NO; + } + + sGULClientQueue = dispatch_queue_create("GULLoggingClientQueue", DISPATCH_QUEUE_SERIAL); + dispatch_set_target_queue(sGULClientQueue, + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); + +#ifdef DEBUG + sMessageCodeRegex = + [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern options:0 error:NULL]; +#endif + }); +} + +void GULSetLoggerLevel(GULLoggerLevel loggerLevel) { + if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) { + GULLogError(kGULLoggerLogger, NO, @"I-COR000023", @"Invalid logger level, %ld", + (long)loggerLevel); + return; + } + GULLoggerInitializeASL(NO, NO); + // We should not raise the logger level if we are running from App Store. + if (loggerLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) { + return; + } + + sGULLoggerMaximumLevel = loggerLevel; + dispatch_async(sGULClientQueue, ^{ + asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(loggerLevel)); + }); +} + +/** + * Check if the level is high enough to be loggable. + */ +__attribute__((no_sanitize("thread"))) BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) { + GULLoggerInitializeASL(NO, NO); + if (sGULLoggerDebugMode) { + return YES; + } + return (BOOL)(loggerLevel <= sGULLoggerMaximumLevel); +} + +#ifdef DEBUG +void GULResetLogger() { + sGULLoggerOnceToken = 0; +} + +aslclient getGULLoggerClient() { + return sGULLoggerClient; +} + +dispatch_queue_t getGULClientQueue() { + return sGULClientQueue; +} + +BOOL getGULLoggerDebugMode() { + return sGULLoggerDebugMode; +} +#endif + +void GULLoggerRegisterVersion(const char *version) { + sVersion = version; +} + +void GULLogBasic(GULLoggerLevel level, + GULLoggerService service, + BOOL forceLog, + NSString *messageCode, + NSString *message, + va_list args_ptr) { + GULLoggerInitializeASL(NO, NO); + if (!(level <= sGULLoggerMaximumLevel || sGULLoggerDebugMode || forceLog)) { + return; + } + +#ifdef DEBUG + NSCAssert(messageCode.length == 11, @"Incorrect message code length."); + NSRange messageCodeRange = NSMakeRange(0, messageCode.length); + NSUInteger numberOfMatches = + [sMessageCodeRegex numberOfMatchesInString:messageCode options:0 range:messageCodeRange]; + NSCAssert(numberOfMatches == 1, @"Incorrect message code format."); +#endif + NSString *logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr]; + logMsg = [NSString stringWithFormat:@"%s - %@[%@] %@", sVersion, service, messageCode, logMsg]; + dispatch_async(sGULClientQueue, ^{ + asl_log(sGULLoggerClient, NULL, level, "%s", logMsg.UTF8String); + }); +} +#pragma clang diagnostic pop + +/** + * Generates the logging functions using macros. + * + * Calling GULLogError(kGULLoggerCore, @"I-COR000001", @"Configure %@ failed.", @"blah") shows: + * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [{service}][I-COR000001] Configure blah failed. + * Calling GULLogDebug(kGULLoggerCore, @"I-COR000001", @"Configure succeed.") shows: + * yyyy-mm-dd hh:mm:ss.SSS sender[PID] [{service}][I-COR000001] Configure succeed. + */ +#define GUL_LOGGING_FUNCTION(level) \ + void GULLog##level(GULLoggerService service, BOOL force, NSString *messageCode, \ + NSString *message, ...) { \ + va_list args_ptr; \ + va_start(args_ptr, message); \ + GULLogBasic(GULLoggerLevel##level, service, force, messageCode, message, args_ptr); \ + va_end(args_ptr); \ + } + +GUL_LOGGING_FUNCTION(Error) +GUL_LOGGING_FUNCTION(Warning) +GUL_LOGGING_FUNCTION(Notice) +GUL_LOGGING_FUNCTION(Info) +GUL_LOGGING_FUNCTION(Debug) + +#undef GUL_MAKE_LOGGER + +#pragma mark - GULLoggerWrapper + +@implementation GULLoggerWrapper + ++ (void)logWithLevel:(GULLoggerLevel)level + withService:(GULLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args { + GULLogBasic(level, service, NO, messageCode, message, args); +} + +@end diff --git a/Firebase/Utilities/Logger/Private/GULLogger.h b/Firebase/Utilities/Logger/Private/GULLogger.h new file mode 100644 index 0000000..c8b12e8 --- /dev/null +++ b/Firebase/Utilities/Logger/Private/GULLogger.h @@ -0,0 +1,151 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The services used in the logger. + */ +typedef NSString *const GULLoggerService; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Initialize GULLogger. + * (required) overrideSTDERR Override the aslOptions to ASL_OPT_STDERR. + * (required) forceDebugMode Force the asl level to ASL_LEVEL_DEBUG. + */ +extern void GULLoggerInitializeASL(BOOL overrideSTDERR, BOOL forceDebugMode); + +/** + * Changes the default logging level of GULLoggerLevelNotice to a user-specified level. + * The default level cannot be set above GULLoggerLevelNotice if the app is running from App Store. + * (required) log level (one of the GULLoggerLevel enum values). + */ +extern void GULSetLoggerLevel(GULLoggerLevel loggerLevel); + +/** + * Checks if the specified logger level is loggable given the current settings. + * (required) log level (one of the GULLoggerLevel enum values). + */ +extern BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel); + +/** + * Register version to include in logs. + * (required) version + */ +extern void GULLoggerRegisterVersion(const char *version); + +/** + * Logs a message to the Xcode console and the device log. If running from AppStore, will + * not log any messages with a level higher than GULLoggerLevelNotice to avoid log spamming. + * (required) log level (one of the GULLoggerLevel enum values). + * (required) service name of type GULLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format + * string. + */ +extern void GULLogBasic(GULLoggerLevel level, + GULLoggerService service, + BOOL forceLog, + NSString *messageCode, + NSString *message, +// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable +// See: http://stackoverflow.com/q/29095469 +#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX + va_list args_ptr +#else + va_list _Nullable args_ptr +#endif +); + +/** + * The following functions accept the following parameters in order: + * (required) service name of type GULLoggerService. + * (required) message code starting from "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * See go/firebase-log-proposal for details. + * (required) message string which can be a format string. + * (optional) the list of arguments to substitute into the format string. + * Example usage: + * GULLogError(kGULLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name); + */ +extern void GULLogError(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogWarning(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogNotice(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogInfo(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); +extern void GULLogDebug(GULLoggerService service, + BOOL force, + NSString *messageCode, + NSString *message, + ...) NS_FORMAT_FUNCTION(4, 5); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +@interface GULLoggerWrapper : NSObject + +/** + * Objective-C wrapper for GULLogBasic to allow weak linking to GULLogger + * (required) log level (one of the GULLoggerLevel enum values). + * (required) service name of type GULLoggerService. + * (required) message code starting with "I-" which means iOS, followed by a capitalized + * three-character service identifier and a six digit integer message ID that is unique + * within the service. + * An example of the message code is @"I-COR000001". + * (required) message string which can be a format string. + * (optional) variable arguments list obtained from calling va_start, used when message is a format + * string. + */ + ++ (void)logWithLevel:(GULLoggerLevel)level + withService:(GULLoggerService)service + withCode:(NSString *)messageCode + withMessage:(NSString *)message + withArgs:(va_list)args; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Utilities/Logger/Public/GULLoggerLevel.h b/Firebase/Utilities/Logger/Public/GULLoggerLevel.h new file mode 100644 index 0000000..09ebf37 --- /dev/null +++ b/Firebase/Utilities/Logger/Public/GULLoggerLevel.h @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The log levels used by internal logging. + */ +typedef NS_ENUM(NSInteger, GULLoggerLevel) { + /** Error level, matches ASL_LEVEL_ERR. */ + GULLoggerLevelError = 3, + /** Warning level, matches ASL_LEVEL_WARNING. */ + GULLoggerLevelWarning = 4, + /** Notice level, matches ASL_LEVEL_NOTICE. */ + GULLoggerLevelNotice = 5, + /** Info level, matches ASL_LEVEL_NOTICE. */ + GULLoggerLevelInfo = 6, + /** Debug level, matches ASL_LEVEL_DEBUG. */ + GULLoggerLevelDebug = 7, + /** Minimum log level. */ + GULLoggerLevelMin = GULLoggerLevelError, + /** Maximum log level. */ + GULLoggerLevelMax = GULLoggerLevelDebug +} NS_SWIFT_NAME(GoogleLoggerLevel); diff --git a/Firebase/Utilities/NSData+zlib/GULNSData+zlib.h b/Firebase/Utilities/NSData+zlib/GULNSData+zlib.h new file mode 100644 index 0000000..36f94a7 --- /dev/null +++ b/Firebase/Utilities/NSData+zlib/GULNSData+zlib.h @@ -0,0 +1,49 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +/// This is a copy of Google Toolbox for Mac library to avoid creating an extra framework. + +// NOTE: For 64bit, none of these apis handle input sizes >32bits, they will return nil when given +// such data. To handle data of that size you really should be streaming it rather then doing it all +// in memory. + +@interface NSData (GULGzip) + +/// Returns an data as the result of decompressing the payload of |data|.The data to decompress must +/// be a gzipped payloads. ++ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error; + +/// Returns an compressed data with the result of gzipping the payload of |data|. Uses the default +/// compression level. ++ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error; + +FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorDomain; +FOUNDATION_EXPORT NSString *const GULNSDataZlibErrorKey; // NSNumber +FOUNDATION_EXPORT NSString *const GULNSDataZlibRemainingBytesKey; // NSNumber + +typedef NS_ENUM(NSInteger, GULNSDataZlibError) { + GULNSDataZlibErrorGreaterThan32BitsToCompress = 1024, + // An internal zlib error. + // GULNSDataZlibErrorKey will contain the error value. + // NSLocalizedDescriptionKey may contain an error string from zlib. + // Look in zlib.h for list of errors. + GULNSDataZlibErrorInternal, + // There was left over data in the buffer that was not used. + // GULNSDataZlibRemainingBytesKey will contain number of remaining bytes. + GULNSDataZlibErrorDataRemaining +}; + +@end diff --git a/Firebase/Utilities/NSData+zlib/GULNSData+zlib.m b/Firebase/Utilities/NSData+zlib/GULNSData+zlib.m new file mode 100644 index 0000000..cd3394a --- /dev/null +++ b/Firebase/Utilities/NSData+zlib/GULNSData+zlib.m @@ -0,0 +1,207 @@ +// Copyright 2018 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "GULNSData+zlib.h" + +#import + +#define kChunkSize 1024 +#define Z_DEFAULT_COMPRESSION (-1) + +NSString *const GULNSDataZlibErrorDomain = @"com.google.GULNSDataZlibErrorDomain"; +NSString *const GULNSDataZlibErrorKey = @"GULNSDataZlibErrorKey"; +NSString *const GULNSDataZlibRemainingBytesKey = @"GULNSDataZlibRemainingBytesKey"; + +@implementation NSData (GULGzip) + ++ (NSData *)gul_dataByInflatingGzippedData:(NSData *)data error:(NSError **)error { + const void *bytes = [data bytes]; + NSUInteger length = [data length]; + if (!bytes || !length) { + return nil; + } + +#if defined(__LP64__) && __LP64__ + // Don't support > 32bit length for 64 bit, see note in header. + if (length > UINT_MAX) { + return nil; + } +#endif + + z_stream strm; + bzero(&strm, sizeof(z_stream)); + + // Setup the input. + strm.avail_in = (unsigned int)length; + strm.next_in = (unsigned char *)bytes; + + int windowBits = 15; // 15 to enable any window size + windowBits += 32; // and +32 to enable zlib or gzip header detection. + + int retCode; + if ((retCode = inflateInit2(&strm, windowBits)) != Z_OK) { + if (error) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + return nil; + } + + // Hint the size at 4x the input size. + NSMutableData *result = [NSMutableData dataWithCapacity:(length * 4)]; + unsigned char output[kChunkSize]; + + // Loop to collect the data. + do { + // Update what we're passing in. + strm.avail_out = kChunkSize; + strm.next_out = output; + retCode = inflate(&strm, Z_NO_FLUSH); + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { + if (error) { + NSMutableDictionary *userInfo = + [NSMutableDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + if (strm.msg) { + NSString *message = [NSString stringWithUTF8String:strm.msg]; + if (message) { + [userInfo setObject:message forKey:NSLocalizedDescriptionKey]; + } + } + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + inflateEnd(&strm); + return nil; + } + // Collect what we got. + unsigned gotBack = kChunkSize - strm.avail_out; + if (gotBack > 0) { + [result appendBytes:output length:gotBack]; + } + + } while (retCode == Z_OK); + + // Make sure there wasn't more data tacked onto the end of a valid compressed stream. + if (strm.avail_in != 0) { + if (error) { + NSDictionary *userInfo = + [NSDictionary dictionaryWithObject:[NSNumber numberWithUnsignedInt:strm.avail_in] + forKey:GULNSDataZlibRemainingBytesKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorDataRemaining + userInfo:userInfo]; + } + result = nil; + } + // The only way out of the loop was by hitting the end of the stream. + NSAssert(retCode == Z_STREAM_END, + @"Thought we finished inflate w/o getting a result of stream end, code %d", retCode); + + // Clean up. + inflateEnd(&strm); + + return result; +} + ++ (NSData *)gul_dataByGzippingData:(NSData *)data error:(NSError **)error { + const void *bytes = [data bytes]; + NSUInteger length = [data length]; + + int level = Z_DEFAULT_COMPRESSION; + if (!bytes || !length) { + return nil; + } + +#if defined(__LP64__) && __LP64__ + // Don't support > 32bit length for 64 bit, see note in header. + if (length > UINT_MAX) { + if (error) { + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorGreaterThan32BitsToCompress + userInfo:nil]; + } + return nil; + } +#endif + + z_stream strm; + bzero(&strm, sizeof(z_stream)); + + int memLevel = 8; // Default. + int windowBits = 15 + 16; // Enable gzip header instead of zlib header. + + int retCode; + if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, + Z_DEFAULT_STRATEGY)) != Z_OK) { + if (error) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + return nil; + } + + // Hint the size at 1/4 the input size. + NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)]; + unsigned char output[kChunkSize]; + + // Setup the input. + strm.avail_in = (unsigned int)length; + strm.next_in = (unsigned char *)bytes; + + // Collect the data. + do { + // update what we're passing in + strm.avail_out = kChunkSize; + strm.next_out = output; + retCode = deflate(&strm, Z_FINISH); + if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) { + if (error) { + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:retCode] + forKey:GULNSDataZlibErrorKey]; + *error = [NSError errorWithDomain:GULNSDataZlibErrorDomain + code:GULNSDataZlibErrorInternal + userInfo:userInfo]; + } + deflateEnd(&strm); + return nil; + } + // Collect what we got. + unsigned gotBack = kChunkSize - strm.avail_out; + if (gotBack > 0) { + [result appendBytes:output length:gotBack]; + } + + } while (retCode == Z_OK); + + // If the loop exits, it used all input and the stream ended. + NSAssert(strm.avail_in == 0, + @"Should have finished deflating without using all input, %u bytes left", strm.avail_in); + NSAssert(retCode == Z_STREAM_END, + @"thought we finished deflate w/o getting a result of stream end, code %d", retCode); + + // Clean up. + deflateEnd(&strm); + + return result; +} + +@end diff --git a/Firebase/Utilities/Network/GULMutableDictionary.m b/Firebase/Utilities/Network/GULMutableDictionary.m new file mode 100644 index 0000000..d281eb4 --- /dev/null +++ b/Firebase/Utilities/Network/GULMutableDictionary.m @@ -0,0 +1,97 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Private/GULMutableDictionary.h" + +@implementation GULMutableDictionary { + /// The mutable dictionary. + NSMutableDictionary *_objects; + + /// Serial synchronization queue. All reads should use dispatch_sync, while writes use + /// dispatch_async. + dispatch_queue_t _queue; +} + +- (instancetype)init { + self = [super init]; + + if (self) { + _objects = [[NSMutableDictionary alloc] init]; + _queue = dispatch_queue_create("GULMutableDictionary", DISPATCH_QUEUE_SERIAL); + } + + return self; +} + +- (NSString *)description { + __block NSString *description; + dispatch_sync(_queue, ^{ + description = self->_objects.description; + }); + return description; +} + +- (id)objectForKey:(id)key { + __block id object; + dispatch_sync(_queue, ^{ + object = self->_objects[key]; + }); + return object; +} + +- (void)setObject:(id)object forKey:(id)key { + dispatch_async(_queue, ^{ + self->_objects[key] = object; + }); +} + +- (void)removeObjectForKey:(id)key { + dispatch_async(_queue, ^{ + [self->_objects removeObjectForKey:key]; + }); +} + +- (void)removeAllObjects { + dispatch_async(_queue, ^{ + [self->_objects removeAllObjects]; + }); +} + +- (NSUInteger)count { + __block NSUInteger count; + dispatch_sync(_queue, ^{ + count = self->_objects.count; + }); + return count; +} + +- (id)objectForKeyedSubscript:(id)key { + // The method this calls is already synchronized. + return [self objectForKey:key]; +} + +- (void)setObject:(id)obj forKeyedSubscript:(id)key { + // The method this calls is already synchronized. + [self setObject:obj forKey:key]; +} + +- (NSDictionary *)dictionary { + __block NSDictionary *dictionary; + dispatch_sync(_queue, ^{ + dictionary = [self->_objects copy]; + }); + return dictionary; +} + +@end diff --git a/Firebase/Utilities/Network/GULNetwork.m b/Firebase/Utilities/Network/GULNetwork.m new file mode 100644 index 0000000..233500b --- /dev/null +++ b/Firebase/Utilities/Network/GULNetwork.m @@ -0,0 +1,388 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Private/GULNetwork.h" +#import "Private/GULNetworkMessageCode.h" + +#import +#import +#import +#import "Private/GULMutableDictionary.h" +#import "Private/GULNetworkConstants.h" + +/// Constant string for request header Content-Encoding. +static NSString *const kGULNetworkContentCompressionKey = @"Content-Encoding"; + +/// Constant string for request header Content-Encoding value. +static NSString *const kGULNetworkContentCompressionValue = @"gzip"; + +/// Constant string for request header Content-Length. +static NSString *const kGULNetworkContentLengthKey = @"Content-Length"; + +/// Constant string for request header Content-Type. +static NSString *const kGULNetworkContentTypeKey = @"Content-Type"; + +/// Constant string for request header Content-Type value. +static NSString *const kGULNetworkContentTypeValue = @"application/x-www-form-urlencoded"; + +/// Constant string for GET request method. +static NSString *const kGULNetworkGETRequestMethod = @"GET"; + +/// Constant string for POST request method. +static NSString *const kGULNetworkPOSTRequestMethod = @"POST"; + +/// Default constant string as a prefix for network logger. +static NSString *const kGULNetworkLogTag = @"Google/Utilities/Network"; + +@interface GULNetwork () +@end + +@implementation GULNetwork { + /// Network reachability. + GULReachabilityChecker *_reachability; + + /// The dictionary of requests by session IDs { NSString : id }. + GULMutableDictionary *_requests; +} + +- (instancetype)init { + return [self initWithReachabilityHost:kGULNetworkReachabilityHost]; +} + +- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost { + self = [super init]; + if (self) { + // Setup reachability. + _reachability = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self + withHost:reachabilityHost]; + if (![_reachability start]) { + return nil; + } + + _requests = [[GULMutableDictionary alloc] init]; + _timeoutInterval = kGULNetworkTimeOutInterval; + } + return self; +} + +- (void)dealloc { + _reachability.reachabilityDelegate = nil; + [_reachability stop]; +} + +#pragma mark - External Methods + ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler:(GULNetworkSystemCompletionHandler)completionHandler { + [GULNetworkURLSession handleEventsForBackgroundURLSessionID:sessionID + completionHandler:completionHandler]; +} + +- (NSString *)postURL:(NSURL *)url + payload:(NSData *)payload + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler { + if (!url.absoluteString.length) { + [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; + return nil; + } + + NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval; + + NSMutableURLRequest *request = + [[NSMutableURLRequest alloc] initWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:timeOutInterval]; + + if (!request) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + NSError *compressError = nil; + NSData *compressedData = [NSData gul_dataByGzippingData:payload error:&compressError]; + if (!compressedData || compressError) { + if (compressError || payload.length > 0) { + // If the payload is not empty but it fails to compress the payload, something has been wrong. + [self handleErrorWithCode:GULErrorCodeNetworkPayloadCompression + queue:queue + withHandler:handler]; + return nil; + } + compressedData = [[NSData alloc] init]; + } + + NSString *postLength = @(compressedData.length).stringValue; + + // Set up the request with the compressed data. + [request setValue:postLength forHTTPHeaderField:kGULNetworkContentLengthKey]; + request.HTTPBody = compressedData; + request.HTTPMethod = kGULNetworkPOSTRequestMethod; + [request setValue:kGULNetworkContentTypeValue forHTTPHeaderField:kGULNetworkContentTypeKey]; + [request setValue:kGULNetworkContentCompressionValue + forHTTPHeaderField:kGULNetworkContentCompressionKey]; + + GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; + fetcher.backgroundNetworkEnabled = usingBackgroundSession; + + __weak GULNetwork *weakSelf = self; + NSString *requestID = [fetcher + sessionIDFromAsyncPOSTRequest:request + completionHandler:^(NSHTTPURLResponse *response, NSData *data, + NSString *sessionID, NSError *error) { + GULNetwork *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); + dispatch_async(queueToDispatch, ^{ + if (sessionID.length) { + [strongSelf->_requests removeObjectForKey:sessionID]; + } + if (handler) { + handler(response, data, error); + } + }); + }]; + if (!requestID) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeNetwork000 + message:@"Uploading data. Host" + context:url]; + _requests[requestID] = fetcher; + return requestID; +} + +- (NSString *)getURL:(NSURL *)url + headers:(NSDictionary *)headers + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler { + if (!url.absoluteString.length) { + [self handleErrorWithCode:GULErrorCodeNetworkInvalidURL queue:queue withHandler:handler]; + return nil; + } + + NSTimeInterval timeOutInterval = _timeoutInterval ?: kGULNetworkTimeOutInterval; + NSMutableURLRequest *request = + [[NSMutableURLRequest alloc] initWithURL:url + cachePolicy:NSURLRequestReloadIgnoringLocalCacheData + timeoutInterval:timeOutInterval]; + + if (!request) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + request.HTTPMethod = kGULNetworkGETRequestMethod; + request.allHTTPHeaderFields = headers; + + GULNetworkURLSession *fetcher = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:self]; + fetcher.backgroundNetworkEnabled = usingBackgroundSession; + + __weak GULNetwork *weakSelf = self; + NSString *requestID = [fetcher + sessionIDFromAsyncGETRequest:request + completionHandler:^(NSHTTPURLResponse *response, NSData *data, NSString *sessionID, + NSError *error) { + GULNetwork *strongSelf = weakSelf; + if (!strongSelf) { + return; + } + dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); + dispatch_async(queueToDispatch, ^{ + if (sessionID.length) { + [strongSelf->_requests removeObjectForKey:sessionID]; + } + if (handler) { + handler(response, data, error); + } + }); + }]; + + if (!requestID) { + [self handleErrorWithCode:GULErrorCodeNetworkSessionTaskCreation + queue:queue + withHandler:handler]; + return nil; + } + + [self GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeNetwork001 + message:@"Downloading data. Host" + context:url]; + _requests[requestID] = fetcher; + return requestID; +} + +- (BOOL)hasUploadInProgress { + return _requests.count > 0; +} + +#pragma mark - Network Reachability + +/// Tells reachability delegate to call reachabilityDidChangeToStatus: to notify the network +/// reachability has changed. +- (void)reachability:(GULReachabilityChecker *)reachability + statusChanged:(GULReachabilityStatus)status { + _networkConnected = (status == kGULReachabilityViaCellular || status == kGULReachabilityViaWifi); + [_reachabilityDelegate reachabilityDidChange]; +} + +#pragma mark - Network logger delegate + +- (void)setLoggerDelegate:(id)loggerDelegate { + // Explicitly check whether the delegate responds to the methods because conformsToProtocol does + // not work correctly even though the delegate does respond to the methods. + if (!loggerDelegate || + ![loggerDelegate + respondsToSelector:@selector(GULNetwork_logWithLevel:messageCode:message:contexts:)] || + ![loggerDelegate + respondsToSelector:@selector(GULNetwork_logWithLevel:messageCode:message:context:)] || + ! + [loggerDelegate respondsToSelector:@selector(GULNetwork_logWithLevel:messageCode:message:)]) { + GULLogError(kGULLoggerNetwork, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork002], + @"Cannot set the network logger delegate: delegate does not conform to the network " + "logger protocol."); + return; + } + _loggerDelegate = loggerDelegate; +} + +#pragma mark - Private methods + +/// Handles network error and calls completion handler with the error. +- (void)handleErrorWithCode:(NSInteger)code + queue:(dispatch_queue_t)queue + withHandler:(GULNetworkCompletionHandler)handler { + NSDictionary *userInfo = @{kGULNetworkErrorContext : @"Failed to create network request"}; + NSError *error = + [[NSError alloc] initWithDomain:kGULNetworkErrorDomain code:code userInfo:userInfo]; + [self GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeNetwork002 + message:@"Failed to create network request. Code, error" + contexts:@[ @(code), error ]]; + if (handler) { + dispatch_queue_t queueToDispatch = queue ? queue : dispatch_get_main_queue(); + dispatch_async(queueToDispatch, ^{ + handler(nil, nil, error); + }); + } +} + +#pragma mark - Network logger + +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + contexts:(NSArray *)contexts { + // Let the delegate log the message if there is a valid logger delegate. Otherwise, just log + // errors/warnings/info messages to the console log. + if (_loggerDelegate) { + [_loggerDelegate GULNetwork_logWithLevel:logLevel + messageCode:messageCode + message:message + contexts:contexts]; + return; + } + if (_isDebugModeEnabled || logLevel == kGULNetworkLogLevelError || + logLevel == kGULNetworkLogLevelWarning || logLevel == kGULNetworkLogLevelInfo) { + NSString *formattedMessage = GULStringWithLogMessage(message, logLevel, contexts); + NSLog(@"%@", formattedMessage); + GULLogBasic((GULLoggerLevel)logLevel, kGULLoggerNetwork, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)messageCode], formattedMessage, + NULL); + } +} + +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + context:(id)context { + if (_loggerDelegate) { + [_loggerDelegate GULNetwork_logWithLevel:logLevel + messageCode:messageCode + message:message + context:context]; + return; + } + NSArray *contexts = context ? @[ context ] : @[]; + [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:contexts]; +} + +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message { + if (_loggerDelegate) { + [_loggerDelegate GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message]; + return; + } + [self GULNetwork_logWithLevel:logLevel messageCode:messageCode message:message contexts:@[]]; +} + +/// Returns a string for the given log level (e.g. kGULNetworkLogLevelError -> @"ERROR"). +static NSString *GULLogLevelDescriptionFromLogLevel(GULNetworkLogLevel logLevel) { + static NSDictionary *levelNames = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + levelNames = @{ + @(kGULNetworkLogLevelError) : @"ERROR", + @(kGULNetworkLogLevelWarning) : @"WARNING", + @(kGULNetworkLogLevelInfo) : @"INFO", + @(kGULNetworkLogLevelDebug) : @"DEBUG" + }; + }); + return levelNames[@(logLevel)]; +} + +/// Returns a formatted string to be used for console logging. +static NSString *GULStringWithLogMessage(NSString *message, + GULNetworkLogLevel logLevel, + NSArray *contexts) { + if (!message) { + message = @"(Message was nil)"; + } else if (!message.length) { + message = @"(Message was empty)"; + } + NSMutableString *result = [[NSMutableString alloc] + initWithFormat:@"<%@/%@> %@", kGULNetworkLogTag, GULLogLevelDescriptionFromLogLevel(logLevel), + message]; + + if (!contexts.count) { + return result; + } + + NSMutableArray *formattedContexts = [[NSMutableArray alloc] init]; + for (id item in contexts) { + [formattedContexts addObject:(item != [NSNull null] ? item : @"(nil)")]; + } + + [result appendString:@": "]; + [result appendString:[formattedContexts componentsJoinedByString:@", "]]; + return result; +} + +@end diff --git a/Firebase/Utilities/Network/GULNetworkConstants.m b/Firebase/Utilities/Network/GULNetworkConstants.m new file mode 100644 index 0000000..90bd03d --- /dev/null +++ b/Firebase/Utilities/Network/GULNetworkConstants.m @@ -0,0 +1,40 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Private/GULNetworkConstants.h" + +#import + +NSString *const kGULNetworkBackgroundSessionConfigIDPrefix = @"com.gul.network.background-upload"; +NSString *const kGULNetworkApplicationSupportSubdirectory = @"GUL/Network"; +NSString *const kGULNetworkTempDirectoryName = @"GULNetworkTemporaryDirectory"; +const NSTimeInterval kGULNetworkTempFolderExpireTime = 60 * 60; // 1 hour +const NSTimeInterval kGULNetworkTimeOutInterval = 60; // 1 minute. +NSString *const kGULNetworkReachabilityHost = @"app-measurement.com"; +NSString *const kGULNetworkErrorContext = @"Context"; + +const int kGULNetworkHTTPStatusOK = 200; +const int kGULNetworkHTTPStatusNoContent = 204; +const int kGULNetworkHTTPStatusCodeMultipleChoices = 300; +const int kGULNetworkHTTPStatusCodeMovedPermanently = 301; +const int kGULNetworkHTTPStatusCodeFound = 302; +const int kGULNetworkHTTPStatusCodeNotModified = 304; +const int kGULNetworkHTTPStatusCodeMovedTemporarily = 307; +const int kGULNetworkHTTPStatusCodeNotFound = 404; +const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic = 429; +const int kGULNetworkHTTPStatusCodeUnavailable = 503; + +NSString *const kGULNetworkErrorDomain = @"com.gul.network.ErrorDomain"; + +GULLoggerService kGULLoggerNetwork = @"[GULNetwork]"; diff --git a/Firebase/Utilities/Network/GULNetworkURLSession.m b/Firebase/Utilities/Network/GULNetworkURLSession.m new file mode 100644 index 0000000..cb8a204 --- /dev/null +++ b/Firebase/Utilities/Network/GULNetworkURLSession.m @@ -0,0 +1,669 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "Private/GULNetworkURLSession.h" + +#import +#import "Private/GULMutableDictionary.h" +#import "Private/GULNetworkConstants.h" +#import "Private/GULNetworkMessageCode.h" + +@implementation GULNetworkURLSession { + /// The handler to be called when the request completes or error has occurs. + GULNetworkURLSessionCompletionHandler _completionHandler; + + /// Session ID generated randomly with a fixed prefix. + NSString *_sessionID; + + /// The session configuration. + NSURLSessionConfiguration *_sessionConfig; + + /// The path to the directory where all temporary files are stored before uploading. + NSURL *_networkDirectoryURL; + + /// The downloaded data from fetching. + NSData *_downloadedData; + + /// The path to the temporary file which stores the uploading data. + NSURL *_uploadingFileURL; + + /// The current request. + NSURLRequest *_request; +} + +#pragma mark - Init + +- (instancetype)initWithNetworkLoggerDelegate:(id)networkLoggerDelegate { + self = [super init]; + if (self) { + // Create URL to the directory where all temporary files to upload have to be stored. + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); + NSString *applicationSupportDirectory = paths.firstObject; + NSArray *tempPathComponents = @[ + applicationSupportDirectory, kGULNetworkApplicationSupportSubdirectory, + kGULNetworkTempDirectoryName + ]; + _networkDirectoryURL = [NSURL fileURLWithPathComponents:tempPathComponents]; + _sessionID = [NSString stringWithFormat:@"%@-%@", kGULNetworkBackgroundSessionConfigIDPrefix, + [[NSUUID UUID] UUIDString]]; + _loggerDelegate = networkLoggerDelegate; + } + return self; +} + +#pragma mark - External Methods + +#pragma mark - To be called from AppDelegate + ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler: + (GULNetworkSystemCompletionHandler)systemCompletionHandler { + // The session may not be Analytics background. Ignore those that do not have the prefix. + if (![sessionID hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) { + return; + } + GULNetworkURLSession *fetcher = [self fetcherWithSessionIdentifier:sessionID]; + if (fetcher != nil) { + [fetcher addSystemCompletionHandler:systemCompletionHandler forSession:sessionID]; + } else { + GULLogError(kGULLoggerNetwork, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)kGULNetworkMessageCodeNetwork003], + @"Failed to retrieve background session with ID %@ after app is relaunched.", + sessionID); + } +} + +#pragma mark - External Methods + +/// Sends an async POST request using NSURLSession for iOS >= 7.0, and returns an ID of the +/// connection. +- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler { + // NSURLSessionUploadTask does not work with NSData in the background. + // To avoid this issue, write the data to a temporary file to upload it. + // Make a temporary file with the data subset. + _uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID]; + NSError *writeError; + NSURLSessionUploadTask *postRequestTask; + NSURLSession *session; + BOOL didWriteFile = NO; + + // Clean up the entire temp folder to avoid temp files that remain in case the previous session + // crashed and did not clean up. + [self maybeRemoveTempFilesAtURL:_networkDirectoryURL + expiringTime:kGULNetworkTempFolderExpireTime]; + + // If there is no background network enabled, no need to write to file. This will allow default + // network session which runs on the foreground. + if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) { + didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path + options:NSDataWritingAtomic + error:&writeError]; + + if (writeError) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession000 + message:@"Failed to write request data to file" + context:writeError]; + } + } + + if (didWriteFile) { + // Exclude this file from backing up to iTunes. There are conflicting reports that excluding + // directory from backing up does not excluding files of that directory from backing up. + [self excludeFromBackupForURL:_uploadingFileURL]; + + _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; + [self populateSessionConfig:_sessionConfig withRequest:request]; + session = [NSURLSession sessionWithConfiguration:_sessionConfig + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + postRequestTask = [session uploadTaskWithRequest:request fromFile:_uploadingFileURL]; + } else { + // If we cannot write to file, just send it in the foreground. + _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + [self populateSessionConfig:_sessionConfig withRequest:request]; + _sessionConfig.URLCache = nil; + session = [NSURLSession sessionWithConfiguration:_sessionConfig + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + postRequestTask = [session uploadTaskWithRequest:request fromData:request.HTTPBody]; + } + + if (!session || !postRequestTask) { + NSError *error = [[NSError alloc] + initWithDomain:kGULNetworkErrorDomain + code:GULErrorCodeNetworkRequestCreation + userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}]; + [self callCompletionHandler:handler withResponse:nil data:nil error:error]; + return nil; + } + + // Save the session into memory. + NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap]; + [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID]; + + _request = [request copy]; + + // Store completion handler because background session does not accept handler block but custom + // delegate. + _completionHandler = [handler copy]; + [postRequestTask resume]; + + return _sessionID; +} + +/// Sends an async GET request using NSURLSession for iOS >= 7.0, and returns an ID of the session. +- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler { + if (_backgroundNetworkEnabled) { + _sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID]; + } else { + _sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + } + + [self populateSessionConfig:_sessionConfig withRequest:request]; + + // Do not cache the GET request. + _sessionConfig.URLCache = nil; + + NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig + delegate:self + delegateQueue:[NSOperationQueue mainQueue]]; + NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request]; + + if (!session || !downloadTask) { + NSError *error = [[NSError alloc] + initWithDomain:kGULNetworkErrorDomain + code:GULErrorCodeNetworkRequestCreation + userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}]; + [self callCompletionHandler:handler withResponse:nil data:nil error:error]; + return nil; + } + + // Save the session into memory. + NSMapTable *sessionIdentifierToFetcherMap = [[self class] sessionIDToFetcherMap]; + [sessionIdentifierToFetcherMap setObject:self forKey:_sessionID]; + + _request = [request copy]; + + _completionHandler = [handler copy]; + [downloadTask resume]; + + return _sessionID; +} + +#pragma mark - NSURLSessionTaskDelegate + +/// Called by the NSURLSession once the download task is completed. The file is saved in the +/// provided URL so we need to read the data and store into _downloadedData. Once the session is +/// completed, URLSession:task:didCompleteWithError will be called and the completion handler will +/// be called with the downloaded data. +- (void)URLSession:(NSURLSession *)session + downloadTask:(NSURLSessionDownloadTask *)task + didFinishDownloadingToURL:(NSURL *)url { + if (!url.path) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession001 + message:@"Unable to read downloaded data from empty temp path"]; + _downloadedData = nil; + return; + } + + NSError *error; + _downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error]; + + if (error) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession002 + message:@"Cannot read the content of downloaded data" + context:error]; + _downloadedData = nil; + } +} + +#if TARGET_OS_IOS || TARGET_OS_TV +- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession003 + message:@"Background session finished" + context:session.configuration.identifier]; + [self callSystemCompletionHandler:session.configuration.identifier]; +} +#endif + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didCompleteWithError:(NSError *)error { + // Avoid any chance of recursive behavior leading to it being used repeatedly. + GULNetworkURLSessionCompletionHandler handler = _completionHandler; + _completionHandler = nil; + + if (task.response) { + // The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7. + NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP"); + + // The server responded so ignore the error created by the system. + error = nil; + } else if (!error) { + error = [[NSError alloc] + initWithDomain:kGULNetworkErrorDomain + code:GULErrorCodeNetworkInvalidResponse + userInfo:@{kGULNetworkErrorContext : @"Network Error: Empty network response"}]; + } + + [self callCompletionHandler:handler + withResponse:(NSHTTPURLResponse *)task.response + data:_downloadedData + error:error]; + + // Remove the temp file to avoid trashing devices with lots of temp files. + [self removeTempItemAtURL:_uploadingFileURL]; + + // Try to clean up stale files again. + [self maybeRemoveTempFilesAtURL:_networkDirectoryURL + expiringTime:kGULNetworkTempFolderExpireTime]; +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, + NSURLCredential *credential))completionHandler { + // The handling is modeled after GTMSessionFetcher. + if ([challenge.protectionSpace.authenticationMethod + isEqualToString:NSURLAuthenticationMethodServerTrust]) { + SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; + if (serverTrust == NULL) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession004 + message:@"Received empty server trust for host. Host" + context:_request.URL]; + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); + return; + } + NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; + if (!credential) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeURLSession005 + message:@"Unable to verify server identity. Host" + context:_request.URL]; + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + return; + } + + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession006 + message:@"Received SSL challenge for host. Host" + context:_request.URL]; + + void (^callback)(BOOL) = ^(BOOL allow) { + if (allow) { + completionHandler(NSURLSessionAuthChallengeUseCredential, credential); + } else { + [self->_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession007 + message:@"Cancelling authentication challenge for host. Host" + context:self->_request.URL]; + completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); + } + }; + + // Retain the trust object to avoid a SecTrustEvaluate() crash on iOS 7. + CFRetain(serverTrust); + + // Evaluate the certificate chain. + // + // The delegate queue may be the main thread. Trust evaluation could cause some + // blocking network activity, so we must evaluate async, as documented at + // https://developer.apple.com/library/ios/technotes/tn2232/ + dispatch_queue_t evaluateBackgroundQueue = + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + + dispatch_async(evaluateBackgroundQueue, ^{ + SecTrustResultType trustEval = kSecTrustResultInvalid; + BOOL shouldAllow; + OSStatus trustError; + + @synchronized([GULNetworkURLSession class]) { + trustError = SecTrustEvaluate(serverTrust, &trustEval); + } + + if (trustError != errSecSuccess) { + [self->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession008 + message:@"Cannot evaluate server trust. Error, host" + contexts:@[ @(trustError), self->_request.URL ]]; + shouldAllow = NO; + } else { + // Having a trust level "unspecified" by the user is the usual result, described at + // https://developer.apple.com/library/mac/qa/qa1360 + shouldAllow = + (trustEval == kSecTrustResultUnspecified || trustEval == kSecTrustResultProceed); + } + + // Call the call back with the permission. + callback(shouldAllow); + + CFRelease(serverTrust); + }); + return; + } + + // Default handling for other Auth Challenges. + completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); +} + +#pragma mark - Internal Methods + +/// Stores system completion handler with session ID as key. +- (void)addSystemCompletionHandler:(GULNetworkSystemCompletionHandler)handler + forSession:(NSString *)identifier { + if (!handler) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession009 + message:@"Cannot store nil system completion handler in network"]; + return; + } + + if (!identifier.length) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession010 + message: + @"Cannot store system completion handler with empty network " + "session identifier"]; + return; + } + + GULMutableDictionary *systemCompletionHandlers = + [[self class] sessionIDToSystemCompletionHandlerDictionary]; + if (systemCompletionHandlers[identifier]) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeURLSession011 + message:@"Got multiple system handlers for a single session ID" + context:identifier]; + } + + systemCompletionHandlers[identifier] = handler; +} + +/// Calls the system provided completion handler with the session ID stored in the dictionary. +/// The handler will be removed from the dictionary after being called. +- (void)callSystemCompletionHandler:(NSString *)identifier { + GULMutableDictionary *systemCompletionHandlers = + [[self class] sessionIDToSystemCompletionHandlerDictionary]; + GULNetworkSystemCompletionHandler handler = [systemCompletionHandlers objectForKey:identifier]; + + if (handler) { + [systemCompletionHandlers removeObjectForKey:identifier]; + + dispatch_async(dispatch_get_main_queue(), ^{ + handler(); + }); + } +} + +/// Sets or updates the session ID of this session. +- (void)setSessionID:(NSString *)sessionID { + _sessionID = [sessionID copy]; +} + +/// Creates a background session configuration with the session ID using the supported method. +- (NSURLSessionConfiguration *)backgroundSessionConfigWithSessionID:(NSString *)sessionID { +#if (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_10) || \ + TARGET_OS_TV || \ + (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0) + + // iOS 8/10.10 builds require the new backgroundSessionConfiguration method name. + return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; + +#elif (TARGET_OS_OSX && defined(MAC_OS_X_VERSION_10_10) && \ + MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10) || \ + (TARGET_OS_IOS && defined(__IPHONE_8_0) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0) + + // Do a runtime check to avoid a deprecation warning about using + // +backgroundSessionConfiguration: on iOS 8. + if ([NSURLSessionConfiguration + respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) { + // Running on iOS 8+/OS X 10.10+. + return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID]; + } else { + // Running on iOS 7/OS X 10.9. + return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; + } + +#else + // Building with an SDK earlier than iOS 8/OS X 10.10. + return [NSURLSessionConfiguration backgroundSessionConfiguration:sessionID]; +#endif +} + +- (void)maybeRemoveTempFilesAtURL:(NSURL *)folderURL expiringTime:(NSTimeInterval)staleTime { + if (!folderURL.absoluteString.length) { + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + + NSArray *properties = @[ NSURLCreationDateKey ]; + NSArray *directoryContent = + [fileManager contentsOfDirectoryAtURL:folderURL + includingPropertiesForKeys:properties + options:NSDirectoryEnumerationSkipsSubdirectoryDescendants + error:&error]; + if (error && error.code != NSFileReadNoSuchFileError) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelDebug + messageCode:kGULNetworkMessageCodeURLSession012 + message:@"Cannot get files from the temporary network folder. Error" + context:error]; + return; + } + + if (!directoryContent.count) { + return; + } + + NSTimeInterval now = [NSDate date].timeIntervalSince1970; + for (NSURL *tempFile in directoryContent) { + NSDate *creationDate; + BOOL getCreationDate = + [tempFile getResourceValue:&creationDate forKey:NSURLCreationDateKey error:NULL]; + if (!getCreationDate) { + continue; + } + NSTimeInterval creationTimeInterval = creationDate.timeIntervalSince1970; + if (fabs(now - creationTimeInterval) > staleTime) { + [self removeTempItemAtURL:tempFile]; + } + } +} + +/// Removes the temporary file written to disk for sending the request. It has to be cleaned up +/// after the session is done. +- (void)removeTempItemAtURL:(NSURL *)fileURL { + if (!fileURL.absoluteString.length) { + return; + } + + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + + if (![fileManager removeItemAtURL:fileURL error:&error] && error.code != NSFileNoSuchFileError) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession013 + message:@"Failed to remove temporary uploading data file. Error" + context:error.localizedDescription]; + } +} + +/// Gets the fetcher with the session ID. ++ (instancetype)fetcherWithSessionIdentifier:(NSString *)sessionIdentifier { + NSMapTable *sessionIdentifierToFetcherMap = [self sessionIDToFetcherMap]; + GULNetworkURLSession *session = [sessionIdentifierToFetcherMap objectForKey:sessionIdentifier]; + if (!session && [sessionIdentifier hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) { + session = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil]; + [session setSessionID:sessionIdentifier]; + [sessionIdentifierToFetcherMap setObject:session forKey:sessionIdentifier]; + } + return session; +} + +/// Returns a map of the fetcher by session ID. Creates a map if it is not created. ++ (NSMapTable *)sessionIDToFetcherMap { + static NSMapTable *sessionIDToFetcherMap; + + static dispatch_once_t sessionMapOnceToken; + dispatch_once(&sessionMapOnceToken, ^{ + sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable]; + }); + return sessionIDToFetcherMap; +} + +/// Returns a map of system provided completion handler by session ID. Creates a map if it is not +/// created. ++ (GULMutableDictionary *)sessionIDToSystemCompletionHandlerDictionary { + static GULMutableDictionary *systemCompletionHandlers; + + static dispatch_once_t systemCompletionHandlerOnceToken; + dispatch_once(&systemCompletionHandlerOnceToken, ^{ + systemCompletionHandlers = [[GULMutableDictionary alloc] init]; + }); + return systemCompletionHandlers; +} + +- (NSURL *)temporaryFilePathWithSessionID:(NSString *)sessionID { + NSString *tempName = [NSString stringWithFormat:@"GULUpload_temp_%@", sessionID]; + return [_networkDirectoryURL URLByAppendingPathComponent:tempName]; +} + +/// Makes sure that the directory to store temp files exists. If not, tries to create it and returns +/// YES. If there is anything wrong, returns NO. +- (BOOL)ensureTemporaryDirectoryExists { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *error = nil; + + // Create a temporary directory if it does not exist or was deleted. + if ([_networkDirectoryURL checkResourceIsReachableAndReturnError:&error]) { + return YES; + } + + if (error && error.code != NSFileReadNoSuchFileError) { + [_loggerDelegate + GULNetwork_logWithLevel:kGULNetworkLogLevelWarning + messageCode:kGULNetworkMessageCodeURLSession014 + message:@"Error while trying to access Network temp folder. Error" + context:error]; + } + + NSError *writeError = nil; + + [fileManager createDirectoryAtURL:_networkDirectoryURL + withIntermediateDirectories:YES + attributes:nil + error:&writeError]; + if (writeError) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession015 + message:@"Cannot create temporary directory. Error" + context:writeError]; + return NO; + } + + // Set the iCloud exclusion attribute on the Documents URL. + [self excludeFromBackupForURL:_networkDirectoryURL]; + + return YES; +} + +- (void)excludeFromBackupForURL:(NSURL *)url { + if (!url.path) { + return; + } + + // Set the iCloud exclusion attribute on the Documents URL. + NSError *preventBackupError = nil; + [url setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&preventBackupError]; + if (preventBackupError) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession016 + message:@"Cannot exclude temporary folder from iTunes backup"]; + } +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler { + NSArray *nonAllowedRedirectionCodes = @[ + @(kGULNetworkHTTPStatusCodeFound), @(kGULNetworkHTTPStatusCodeMovedPermanently), + @(kGULNetworkHTTPStatusCodeMovedTemporarily), @(kGULNetworkHTTPStatusCodeMultipleChoices) + ]; + + // Allow those not in the non allowed list to be followed. + if (![nonAllowedRedirectionCodes containsObject:@(response.statusCode)]) { + completionHandler(request); + return; + } + + // Do not allow redirection if the response code is in the non-allowed list. + NSURLRequest *newRequest = request; + + if (response) { + newRequest = nil; + } + + completionHandler(newRequest); +} + +#pragma mark - Helper Methods + +- (void)callCompletionHandler:(GULNetworkURLSessionCompletionHandler)handler + withResponse:(NSHTTPURLResponse *)response + data:(NSData *)data + error:(NSError *)error { + if (error) { + [_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError + messageCode:kGULNetworkMessageCodeURLSession017 + message:@"Encounter network error. Code, error" + contexts:@[ @(error.code), error ]]; + } + + if (handler) { + dispatch_async(dispatch_get_main_queue(), ^{ + handler(response, data, self->_sessionID, error); + }); + } +} + +- (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig + withRequest:(NSURLRequest *)request { + sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields; + sessionConfig.timeoutIntervalForRequest = request.timeoutInterval; + sessionConfig.timeoutIntervalForResource = request.timeoutInterval; + sessionConfig.requestCachePolicy = request.cachePolicy; +} + +@end diff --git a/Firebase/Utilities/Network/Private/GULMutableDictionary.h b/Firebase/Utilities/Network/Private/GULMutableDictionary.h new file mode 100644 index 0000000..a8cc45b --- /dev/null +++ b/Firebase/Utilities/Network/Private/GULMutableDictionary.h @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +/// A mutable dictionary that provides atomic accessor and mutators. +@interface GULMutableDictionary : NSObject + +/// Returns an object given a key in the dictionary or nil if not found. +- (id)objectForKey:(id)key; + +/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. +- (void)setObject:(id)object forKey:(id)key; + +/// Removes the object given its session ID from the dictionary. +- (void)removeObjectForKey:(id)key; + +/// Removes all objects. +- (void)removeAllObjects; + +/// Returns the number of current objects in the dictionary. +- (NSUInteger)count; + +/// Returns an object given a key in the dictionary or nil if not found. +- (id)objectForKeyedSubscript:(id)key; + +/// Updates the object given its key or adds it to the dictionary if it is not in the dictionary. +- (void)setObject:(id)obj forKeyedSubscript:(id)key; + +/// Returns the immutable dictionary. +- (NSDictionary *)dictionary; + +@end diff --git a/Firebase/Utilities/Network/Private/GULNetwork.h b/Firebase/Utilities/Network/Private/GULNetwork.h new file mode 100644 index 0000000..0e75ae5 --- /dev/null +++ b/Firebase/Utilities/Network/Private/GULNetwork.h @@ -0,0 +1,87 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "GULNetworkConstants.h" +#import "GULNetworkLoggerProtocol.h" +#import "GULNetworkURLSession.h" + +/// Delegate protocol for GULNetwork events. +@protocol GULNetworkReachabilityDelegate + +/// Tells the delegate to handle events when the network reachability changes to connected or not +/// connected. +- (void)reachabilityDidChange; + +@end + +/// The Network component that provides network status and handles network requests and responses. +/// This is not thread safe. +/// +/// NOTE: +/// User must add FIRAnalytics handleEventsForBackgroundURLSessionID:completionHandler to the +/// AppDelegate application:handleEventsForBackgroundURLSession:completionHandler: +@interface GULNetwork : NSObject + +/// Indicates if network connectivity is available. +@property(nonatomic, readonly, getter=isNetworkConnected) BOOL networkConnected; + +/// Indicates if there are any uploads in progress. +@property(nonatomic, readonly, getter=hasUploadInProgress) BOOL uploadInProgress; + +/// An optional delegate that can be used in the event when network reachability changes. +@property(nonatomic, weak) id reachabilityDelegate; + +/// An optional delegate that can be used to log messages, warnings or errors that occur in the +/// network operations. +@property(nonatomic, weak) id loggerDelegate; + +/// Indicates whether the logger should display debug messages. +@property(nonatomic, assign) BOOL isDebugModeEnabled; + +/// The time interval in seconds for the network request to timeout. +@property(nonatomic, assign) NSTimeInterval timeoutInterval; + +/// Initializes with the default reachability host. +- (instancetype)init; + +/// Initializes with a custom reachability host. +- (instancetype)initWithReachabilityHost:(NSString *)reachabilityHost; + +/// Handles events when background session with the given ID has finished. ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; + +/// Compresses and sends a POST request with the provided data to the URL. The session will be +/// background session if usingBackgroundSession is YES. Otherwise, the POST session is default +/// session. Returns a session ID or nil if an error occurs. +- (NSString *)postURL:(NSURL *)url + payload:(NSData *)payload + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler; + +/// Sends a GET request with the provided data to the URL. The session will be background session +/// if usingBackgroundSession is YES. Otherwise, the GET session is default session. Returns a +/// session ID or nil if an error occurs. +- (NSString *)getURL:(NSURL *)url + headers:(NSDictionary *)headers + queue:(dispatch_queue_t)queue + usingBackgroundSession:(BOOL)usingBackgroundSession + completionHandler:(GULNetworkCompletionHandler)handler; + +@end diff --git a/Firebase/Utilities/Network/Private/GULNetworkConstants.h b/Firebase/Utilities/Network/Private/GULNetworkConstants.h new file mode 100644 index 0000000..44d440b --- /dev/null +++ b/Firebase/Utilities/Network/Private/GULNetworkConstants.h @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +/// Error codes in Firebase Network error domain. +/// Note: these error codes should never change. It would make it harder to decode the errors if +/// we inadvertently altered any of these codes in a future SDK version. +typedef NS_ENUM(NSInteger, GULNetworkErrorCode) { + /// Unknown error. + GULNetworkErrorCodeUnknown = 0, + /// Error occurs when the request URL is invalid. + GULErrorCodeNetworkInvalidURL = 1, + /// Error occurs when request cannot be constructed. + GULErrorCodeNetworkRequestCreation = 2, + /// Error occurs when payload cannot be compressed. + GULErrorCodeNetworkPayloadCompression = 3, + /// Error occurs when session task cannot be created. + GULErrorCodeNetworkSessionTaskCreation = 4, + /// Error occurs when there is no response. + GULErrorCodeNetworkInvalidResponse = 5 +}; + +#pragma mark - Network constants + +/// The prefix of the ID of the background session. +extern NSString *const kGULNetworkBackgroundSessionConfigIDPrefix; + +/// The sub directory to store the files of data that is being uploaded in the background. +extern NSString *const kGULNetworkApplicationSupportSubdirectory; + +/// Name of the temporary directory that stores files for background uploading. +extern NSString *const kGULNetworkTempDirectoryName; + +/// The period when the temporary uploading file can stay. +extern const NSTimeInterval kGULNetworkTempFolderExpireTime; + +/// The default network request timeout interval. +extern const NSTimeInterval kGULNetworkTimeOutInterval; + +/// The host to check the reachability of the network. +extern NSString *const kGULNetworkReachabilityHost; + +/// The key to get the error context of the UserInfo. +extern NSString *const kGULNetworkErrorContext; + +#pragma mark - Network Status Code + +extern const int kGULNetworkHTTPStatusOK; +extern const int kGULNetworkHTTPStatusNoContent; +extern const int kGULNetworkHTTPStatusCodeMultipleChoices; +extern const int kGULNetworkHTTPStatusCodeMovedPermanently; +extern const int kGULNetworkHTTPStatusCodeFound; +extern const int kGULNetworkHTTPStatusCodeNotModified; +extern const int kGULNetworkHTTPStatusCodeMovedTemporarily; +extern const int kGULNetworkHTTPStatusCodeNotFound; +extern const int kGULNetworkHTTPStatusCodeCannotAcceptTraffic; +extern const int kGULNetworkHTTPStatusCodeUnavailable; + +#pragma mark - Error Domain + +extern NSString *const kGULNetworkErrorDomain; + +/// The logger service for GULNetwork. +extern GULLoggerService kGULLoggerNetwork; diff --git a/Firebase/Utilities/Network/Private/GULNetworkLoggerProtocol.h b/Firebase/Utilities/Network/Private/GULNetworkLoggerProtocol.h new file mode 100644 index 0000000..f1be590 --- /dev/null +++ b/Firebase/Utilities/Network/Private/GULNetworkLoggerProtocol.h @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import + +#import "GULNetworkMessageCode.h" + +/// The log levels used by GULNetworkLogger. +typedef NS_ENUM(NSInteger, GULNetworkLogLevel) { + kGULNetworkLogLevelError = GULLoggerLevelError, + kGULNetworkLogLevelWarning = GULLoggerLevelWarning, + kGULNetworkLogLevelInfo = GULLoggerLevelInfo, + kGULNetworkLogLevelDebug = GULLoggerLevelDebug, +}; + +@protocol GULNetworkLoggerDelegate + +@required +/// Tells the delegate to log a message with an array of contexts and the log level. +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + contexts:(NSArray *)contexts; + +/// Tells the delegate to log a message with a context and the log level. +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message + context:(id)context; + +/// Tells the delegate to log a message with the log level. +- (void)GULNetwork_logWithLevel:(GULNetworkLogLevel)logLevel + messageCode:(GULNetworkMessageCode)messageCode + message:(NSString *)message; + +@end diff --git a/Firebase/Utilities/Network/Private/GULNetworkMessageCode.h b/Firebase/Utilities/Network/Private/GULNetworkMessageCode.h new file mode 100644 index 0000000..ce78e60 --- /dev/null +++ b/Firebase/Utilities/Network/Private/GULNetworkMessageCode.h @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. +typedef NS_ENUM(NSInteger, GULNetworkMessageCode) { + // GULNetwork.m + kGULNetworkMessageCodeNetwork000 = 900000, // I-NET900000 + kGULNetworkMessageCodeNetwork001 = 900001, // I-NET900001 + kGULNetworkMessageCodeNetwork002 = 900002, // I-NET900002 + kGULNetworkMessageCodeNetwork003 = 900003, // I-NET900003 + // GULNetworkURLSession.m + kGULNetworkMessageCodeURLSession000 = 901000, // I-NET901000 + kGULNetworkMessageCodeURLSession001 = 901001, // I-NET901001 + kGULNetworkMessageCodeURLSession002 = 901002, // I-NET901002 + kGULNetworkMessageCodeURLSession003 = 901003, // I-NET901003 + kGULNetworkMessageCodeURLSession004 = 901004, // I-NET901004 + kGULNetworkMessageCodeURLSession005 = 901005, // I-NET901005 + kGULNetworkMessageCodeURLSession006 = 901006, // I-NET901006 + kGULNetworkMessageCodeURLSession007 = 901007, // I-NET901007 + kGULNetworkMessageCodeURLSession008 = 901008, // I-NET901008 + kGULNetworkMessageCodeURLSession009 = 901009, // I-NET901009 + kGULNetworkMessageCodeURLSession010 = 901010, // I-NET901010 + kGULNetworkMessageCodeURLSession011 = 901011, // I-NET901011 + kGULNetworkMessageCodeURLSession012 = 901012, // I-NET901012 + kGULNetworkMessageCodeURLSession013 = 901013, // I-NET901013 + kGULNetworkMessageCodeURLSession014 = 901014, // I-NET901014 + kGULNetworkMessageCodeURLSession015 = 901015, // I-NET901015 + kGULNetworkMessageCodeURLSession016 = 901016, // I-NET901016 + kGULNetworkMessageCodeURLSession017 = 901017, // I-NET901017 + kGULNetworkMessageCodeURLSession018 = 901018, // I-NET901018 +}; diff --git a/Firebase/Utilities/Network/Private/GULNetworkURLSession.h b/Firebase/Utilities/Network/Private/GULNetworkURLSession.h new file mode 100644 index 0000000..81190c6 --- /dev/null +++ b/Firebase/Utilities/Network/Private/GULNetworkURLSession.h @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#import "GULNetworkLoggerProtocol.h" + +typedef void (^GULNetworkCompletionHandler)(NSHTTPURLResponse *response, + NSData *data, + NSError *error); +typedef void (^GULNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *response, + NSData *data, + NSString *sessionID, + NSError *error); +typedef void (^GULNetworkSystemCompletionHandler)(void); + +/// The protocol that uses NSURLSession for iOS >= 7.0 to handle requests and responses. +@interface GULNetworkURLSession + : NSObject + +/// Indicates whether the background network is enabled. Default value is NO. +@property(nonatomic, getter=isBackgroundNetworkEnabled) BOOL backgroundNetworkEnabled; + +/// The logger delegate to log message, errors or warnings that occur during the network operations. +@property(nonatomic, weak) id loggerDelegate; + +/// Calls the system provided completion handler after the background session is finished. ++ (void)handleEventsForBackgroundURLSessionID:(NSString *)sessionID + completionHandler:(GULNetworkSystemCompletionHandler)completionHandler; + +/// Initializes with logger delegate. +- (instancetype)initWithNetworkLoggerDelegate:(id)networkLoggerDelegate + NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/// Sends an asynchronous POST request and calls the provided completion handler when the request +/// completes or when errors occur, and returns an ID of the session/connection. +- (NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler; + +/// Sends an asynchronous GET request and calls the provided completion handler when the request +/// completes or when errors occur, and returns an ID of the session. +- (NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request + completionHandler:(GULNetworkURLSessionCompletionHandler)handler; + +@end diff --git a/Firebase/Utilities/Reachability/GULReachabilityChecker+Internal.h b/Firebase/Utilities/Reachability/GULReachabilityChecker+Internal.h new file mode 100644 index 0000000..8883c4d --- /dev/null +++ b/Firebase/Utilities/Reachability/GULReachabilityChecker+Internal.h @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +typedef SCNetworkReachabilityRef (*GULReachabilityCreateWithNameFn)(CFAllocatorRef allocator, + const char *host); + +typedef Boolean (*GULReachabilitySetCallbackFn)(SCNetworkReachabilityRef target, + SCNetworkReachabilityCallBack callback, + SCNetworkReachabilityContext *context); +typedef Boolean (*GULReachabilityScheduleWithRunLoopFn)(SCNetworkReachabilityRef target, + CFRunLoopRef runLoop, + CFStringRef runLoopMode); +typedef Boolean (*GULReachabilityUnscheduleFromRunLoopFn)(SCNetworkReachabilityRef target, + CFRunLoopRef runLoop, + CFStringRef runLoopMode); + +typedef void (*GULReachabilityReleaseFn)(CFTypeRef cf); + +struct GULReachabilityApi { + GULReachabilityCreateWithNameFn createWithNameFn; + GULReachabilitySetCallbackFn setCallbackFn; + GULReachabilityScheduleWithRunLoopFn scheduleWithRunLoopFn; + GULReachabilityUnscheduleFromRunLoopFn unscheduleFromRunLoopFn; + GULReachabilityReleaseFn releaseFn; +}; + +@interface GULReachabilityChecker (Internal) + +- (const struct GULReachabilityApi *)reachabilityApi; +- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi; + +@end diff --git a/Firebase/Utilities/Reachability/GULReachabilityChecker.m b/Firebase/Utilities/Reachability/GULReachabilityChecker.m new file mode 100644 index 0000000..1ddacdf --- /dev/null +++ b/Firebase/Utilities/Reachability/GULReachabilityChecker.m @@ -0,0 +1,240 @@ +// Copyright 2017 Google +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import + +#import "GULReachabilityChecker+Internal.h" +#import "Private/GULReachabilityChecker.h" +#import "Private/GULReachabilityMessageCode.h" + +#import +#import + +static GULLoggerService kGULLoggerReachability = @"[GULReachability]"; + +static void ReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, + void *info); + +static const struct GULReachabilityApi kGULDefaultReachabilityApi = { + SCNetworkReachabilityCreateWithName, + SCNetworkReachabilitySetCallback, + SCNetworkReachabilityScheduleWithRunLoop, + SCNetworkReachabilityUnscheduleFromRunLoop, + CFRelease, +}; + +static NSString *const kGULReachabilityUnknownStatus = @"Unknown"; +static NSString *const kGULReachabilityConnectedStatus = @"Connected"; +static NSString *const kGULReachabilityDisconnectedStatus = @"Disconnected"; + +@interface GULReachabilityChecker () + +@property(nonatomic, assign) const struct GULReachabilityApi *reachabilityApi; +@property(nonatomic, assign) GULReachabilityStatus reachabilityStatus; +@property(nonatomic, copy) NSString *host; +@property(nonatomic, assign) SCNetworkReachabilityRef reachability; + +@end + +@implementation GULReachabilityChecker + +@synthesize reachabilityApi = reachabilityApi_; +@synthesize reachability = reachability_; + +- (const struct GULReachabilityApi *)reachabilityApi { + return reachabilityApi_; +} + +- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi { + if (reachability_) { + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000], + @"Cannot change reachability API while reachability is running. " + @"Call stop first."); + return; + } + reachabilityApi_ = reachabilityApi; +} + +@synthesize reachabilityStatus = reachabilityStatus_; +@synthesize host = host_; +@synthesize reachabilityDelegate = reachabilityDelegate_; + +- (BOOL)isActive { + return reachability_ != nil; +} + +- (void)setReachabilityDelegate:(id)reachabilityDelegate { + if (reachabilityDelegate && + (![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) { + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-NET%06ld", (long)kGULReachabilityMessageCode005], + @"Reachability delegate doesn't conform to Reachability protocol."); + return; + } + reachabilityDelegate_ = reachabilityDelegate; +} + +- (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate + withHost:(NSString *)host { + self = [super init]; + + if (!host || !host.length) { + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001], + @"Invalid host specified"); + return nil; + } + if (self) { + [self setReachabilityDelegate:reachabilityDelegate]; + reachabilityApi_ = &kGULDefaultReachabilityApi; + reachabilityStatus_ = kGULReachabilityUnknown; + host_ = [host copy]; + reachability_ = nil; + } + return self; +} + +- (void)dealloc { + reachabilityDelegate_ = nil; + [self stop]; +} + +- (BOOL)start { + if (!reachability_) { + reachability_ = reachabilityApi_->createWithNameFn(kCFAllocatorDefault, [host_ UTF8String]); + if (!reachability_) { + return NO; + } + SCNetworkReachabilityContext context = { + 0, /* version */ + (__bridge void *)(self), /* info (passed as last parameter to reachability callback) */ + NULL, /* retain */ + NULL, /* release */ + NULL /* copyDescription */ + }; + if (!reachabilityApi_->setCallbackFn(reachability_, ReachabilityCallback, &context) || + !reachabilityApi_->scheduleWithRunLoopFn(reachability_, CFRunLoopGetMain(), + kCFRunLoopCommonModes)) { + reachabilityApi_->releaseFn(reachability_); + reachability_ = nil; + + GULLogError(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002], + @"Failed to start reachability handle"); + return NO; + } + } + GULLogDebug(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003], + @"Monitoring the network status"); + return YES; +} + +- (void)stop { + if (reachability_) { + reachabilityStatus_ = kGULReachabilityUnknown; + reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(), + kCFRunLoopCommonModes); + reachabilityApi_->releaseFn(reachability_); + reachability_ = nil; + } +} + +- (GULReachabilityStatus)statusForFlags:(SCNetworkReachabilityFlags)flags { + GULReachabilityStatus status = kGULReachabilityNotReachable; + // If the Reachable flag is not set, we definitely don't have connectivity. + if (flags & kSCNetworkReachabilityFlagsReachable) { + // Reachable flag is set. Check further flags. + if (!(flags & kSCNetworkReachabilityFlagsConnectionRequired)) { +// Connection required flag is not set, so we have connectivity. +#if TARGET_OS_IOS || TARGET_OS_TV + status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular + : kGULReachabilityViaWifi; +#elif TARGET_OS_OSX + status = kGULReachabilityViaWifi; +#endif + } else if ((flags & (kSCNetworkReachabilityFlagsConnectionOnDemand | + kSCNetworkReachabilityFlagsConnectionOnTraffic)) && + !(flags & kSCNetworkReachabilityFlagsInterventionRequired)) { +// If the connection on demand or connection on traffic flag is set, and user intervention +// is not required, we have connectivity. +#if TARGET_OS_IOS || TARGET_OS_TV + status = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? kGULReachabilityViaCellular + : kGULReachabilityViaWifi; +#elif TARGET_OS_OSX + status = kGULReachabilityViaWifi; +#endif + } + } + return status; +} + +- (void)reachabilityFlagsChanged:(SCNetworkReachabilityFlags)flags { + GULReachabilityStatus status = [self statusForFlags:flags]; + if (reachabilityStatus_ != status) { + NSString *reachabilityStatusString; + if (status == kGULReachabilityUnknown) { + reachabilityStatusString = kGULReachabilityUnknownStatus; + } else { + reachabilityStatusString = (status == kGULReachabilityNotReachable) + ? kGULReachabilityDisconnectedStatus + : kGULReachabilityConnectedStatus; + } + + GULLogDebug(kGULLoggerReachability, NO, + [NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004], + @"Network status has changed. Code:%@, status:%@", @(status), + reachabilityStatusString); + reachabilityStatus_ = status; + [reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_]; + } +} + +@end + +static void ReachabilityCallback(SCNetworkReachabilityRef reachability, + SCNetworkReachabilityFlags flags, + void *info) { + GULReachabilityChecker *checker = (__bridge GULReachabilityChecker *)info; + [checker reachabilityFlagsChanged:flags]; +} + +// This function used to be at the top of the file, but it was moved here +// as a workaround for a suspected compiler bug. When compiled in Release mode +// and run on an iOS device with WiFi disabled, the reachability code crashed +// when calling SCNetworkReachabilityScheduleWithRunLoop, or shortly thereafter. +// After unsuccessfully trying to diagnose the cause of the crash, it was +// discovered that moving this function to the end of the file magically fixed +// the crash. If you are going to edit this file, exercise caution and make sure +// to test thoroughly with an iOS device under various network conditions. +const NSString *GULReachabilityStatusString(GULReachabilityStatus status) { + switch (status) { + case kGULReachabilityUnknown: + return @"Reachability Unknown"; + + case kGULReachabilityNotReachable: + return @"Not reachable"; + + case kGULReachabilityViaWifi: + return @"Reachable via Wifi"; + + case kGULReachabilityViaCellular: + return @"Reachable via Cellular Data"; + + default: + return [NSString stringWithFormat:@"Invalid reachability status %d", (int)status]; + } +} diff --git a/Firebase/Utilities/Reachability/Private/GULReachabilityChecker.h b/Firebase/Utilities/Reachability/Private/GULReachabilityChecker.h new file mode 100644 index 0000000..b317a0b --- /dev/null +++ b/Firebase/Utilities/Reachability/Private/GULReachabilityChecker.h @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import +#import + +/// Reachability Status +typedef enum { + kGULReachabilityUnknown, ///< Have not yet checked or been notified whether host is reachable. + kGULReachabilityNotReachable, ///< Host is not reachable. + kGULReachabilityViaWifi, ///< Host is reachable via Wifi. + kGULReachabilityViaCellular, ///< Host is reachable via cellular. +} GULReachabilityStatus; + +const NSString *GULReachabilityStatusString(GULReachabilityStatus status); + +@class GULReachabilityChecker; + +/// Google Analytics iOS Reachability Checker. +@protocol GULReachabilityDelegate +@required +/// Called when network status has changed. +- (void)reachability:(GULReachabilityChecker *)reachability + statusChanged:(GULReachabilityStatus)status; +@end + +/// Google Analytics iOS Network Status Checker. +@interface GULReachabilityChecker : NSObject + +/// The last known reachability status, or GULReachabilityStatusUnknown if the +/// checker is not active. +@property(nonatomic, readonly) GULReachabilityStatus reachabilityStatus; +/// The host to which reachability status is to be checked. +@property(nonatomic, copy, readonly) NSString *host; +/// The delegate to be notified of reachability status changes. +@property(nonatomic, weak) id reachabilityDelegate; +/// `YES` if the reachability checker is active, `NO` otherwise. +@property(nonatomic, readonly) BOOL isActive; + +/// Initialize the reachability checker. Note that you must call start to begin checking for and +/// receiving notifications about network status changes. +/// +/// @param reachabilityDelegate The delegate to be notified when reachability status to host +/// changes. +/// +/// @param host The name of the host. +/// +- (instancetype)initWithReachabilityDelegate:(id)reachabilityDelegate + withHost:(NSString *)host; + +- (instancetype)init NS_UNAVAILABLE; + +/// Start checking for reachability to the specified host. This has no effect if the status +/// checker is already checking for connectivity. +/// +/// @return `YES` if initiating status checking was successful or the status checking has already +/// been initiated, `NO` otherwise. +- (BOOL)start; + +/// Stop checking for reachability to the specified host. This has no effect if the status +/// checker is not checking for connectivity. +- (void)stop; + +@end diff --git a/Firebase/Utilities/Reachability/Private/GULReachabilityMessageCode.h b/Firebase/Utilities/Reachability/Private/GULReachabilityMessageCode.h new file mode 100644 index 0000000..283cdd5 --- /dev/null +++ b/Firebase/Utilities/Reachability/Private/GULReachabilityMessageCode.h @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Make sure these codes do not overlap with any contained in the FIRAMessageCode enum. +typedef NS_ENUM(NSInteger, GULReachabilityMessageCode) { + // GULReachabilityChecker.m + kGULReachabilityMessageCode000 = 902000, // I-NET902000 + kGULReachabilityMessageCode001 = 902001, // I-NET902001 + kGULReachabilityMessageCode002 = 902002, // I-NET902002 + kGULReachabilityMessageCode003 = 902003, // I-NET902003 + kGULReachabilityMessageCode004 = 902004, // I-NET902004 + kGULReachabilityMessageCode005 = 902005, // I-NET902005 + kGULReachabilityMessageCode006 = 902006, // I-NET902006 +}; diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 43c565f..47e211b 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -62,5 +62,6 @@ supports email and password accounts, as well as several 3rd party authenticatio s.framework = 'SafariServices' s.framework = 'Security' s.dependency 'FirebaseCore', '~> 5.0' + s.dependency 'GoogleUtilities/Environment', '~> 5.0' s.dependency 'GTMSessionFetcher/Core', '~> 1.1' end diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index 8aa0b23..9d6fccf 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -13,7 +13,9 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration s.source = { :git => 'https://github.com/firebase/firebase-ios-sdk.git', - :tag => 'Core-' + s.version.to_s +# Undo comment before release +# :tag => 'Core-' + s.version.to_s + :tag => 'pre-5.3-' + s.version.to_s } s.social_media_url = 'https://twitter.com/Firebase' s.ios.deployment_target = '8.0' @@ -26,12 +28,11 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration s.source_files = 'Firebase/Core/**/*.[mh]' s.public_header_files = 'Firebase/Core/Public/*.h', 'Firebase/Core/Private/*.h' - s.private_header_files = 'Firebase/Core/Private/*.h', 'Firebase/Core/third_party/*.h' + s.private_header_files = 'Firebase/Core/Private/*.h' s.frameworks = [ - 'Foundation', - 'SystemConfiguration' + 'Foundation' ] - s.dependency 'GoogleToolboxForMac/NSData+zlib', '~> 2.1' + s.dependency 'GoogleUtilities/Logger', '~> 5.0' s.pod_target_xcconfig = { 'OTHER_CFLAGS' => '-fno-autolink', 'GCC_PREPROCESSOR_DEFINITIONS' => diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 77ec0b3..cb8737f 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -38,6 +38,6 @@ device, and it is completely free. s.framework = 'SystemConfiguration' s.dependency 'FirebaseCore', '~> 5.0' s.dependency 'FirebaseInstanceID', '~> 3.0' - s.dependency 'GoogleToolboxForMac/Logger', '~> 2.1' + s.dependency 'GoogleUtilities/Reachability', '~> 5.0' s.dependency 'Protobuf', '~> 3.1' end diff --git a/Firestore/CMakeLists.txt b/Firestore/CMakeLists.txt index d5027ce..c74313c 100644 --- a/Firestore/CMakeLists.txt +++ b/Firestore/CMakeLists.txt @@ -85,6 +85,7 @@ find_package(Protobuf REQUIRED) find_package(GRPC REQUIRED) if(APPLE) + find_package(GoogleUtilities REQUIRED) find_package(FirebaseCore REQUIRED) endif() diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index d15e590..7aef00c 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -1429,7 +1429,6 @@ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-SwiftBuildTest/Pods-Firestore_Example_iOS-SwiftBuildTest-frameworks.sh", "${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", - "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", "${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework", "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework", @@ -1442,7 +1441,6 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRPCClient.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework", @@ -1465,7 +1463,6 @@ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", - "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", "${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework", "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework", @@ -1478,7 +1475,6 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRPCClient.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework", @@ -1669,7 +1665,6 @@ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh", "${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework", "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", - "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework", "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", "${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework", "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework", @@ -1682,7 +1677,6 @@ outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRPCClient.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework", diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile index 3095b19..86cbd82 100644 --- a/Firestore/Example/Podfile +++ b/Firestore/Example/Podfile @@ -10,10 +10,11 @@ target 'Firestore_Example_iOS' do # The next line is the forcing function for the Firebase pod. The Firebase # version's subspecs should depend on the component versions in their # corresponding podspec's. - pod 'Firebase/Core', '5.4.0' + pod 'Firebase/CoreOnly', '5.4.0' pod 'FirebaseAuth', :path => '../../' pod 'FirebaseCore', :path => '../../' + pod 'GoogleUtilities', :path => '../../' pod 'FirebaseFirestore', :path => '../../' target 'Firestore_Tests_iOS' do diff --git a/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt b/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt index 2241fae..92f1de1 100644 --- a/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/auth/CMakeLists.txt @@ -33,6 +33,7 @@ cc_library( firebase_credentials_provider_apple.mm DEPENDS FirebaseCore + GoogleUtilities firebase_firestore_auth_base EXCLUDE_FROM_ALL ) diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt index 5b38b2e..228b0c0 100644 --- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt @@ -55,6 +55,7 @@ cc_library( string_apple.h DEPENDS FirebaseCore + GoogleUtilities absl_strings firebase_firestore_util_base EXCLUDE_FROM_ALL diff --git a/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt b/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt index 636acb0..9d6e4a7 100644 --- a/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/testutil/CMakeLists.txt @@ -19,6 +19,7 @@ cc_library( app_testing.mm DEPENDS FirebaseCore + GoogleUtilities absl_strings EXCLUDE_FROM_ALL ) diff --git a/Functions/Example/Podfile b/Functions/Example/Podfile index ea07e11..6761062 100644 --- a/Functions/Example/Podfile +++ b/Functions/Example/Podfile @@ -4,6 +4,7 @@ target 'FirebaseFunctions_Example' do platform :ios, '8.0' pod 'FirebaseCore', :path => '../../' + pod 'GoogleUtilities', :path => '../../' pod 'FirebaseFunctions', :path => '../../' target 'FirebaseFunctions_Tests' do diff --git a/GoogleUtilities.podspec b/GoogleUtilities.podspec new file mode 100644 index 0000000..2dd72f3 --- /dev/null +++ b/GoogleUtilities.podspec @@ -0,0 +1,71 @@ +Pod::Spec.new do |s| + s.name = 'GoogleUtilities' + s.version = '5.0.5' + s.summary = 'Google Utilities for iOS (plus community support for macOS and tvOS)' + + s.description = <<-DESC +Internal Google Utilities including Network, Reachability Environment, Logger, and Swizzling for +other Google CocoaPods. They're not intended for direct public usage. + DESC + +# TODO update homepage link with GoogleUtilities is moved to another repo. + s.homepage = 'https://github.com/firebase/firebase-ios-sdk' + s.license = { :type => 'Apache', :file => 'LICENSE' } + s.authors = 'Google, Inc.' + + s.source = { + :git => 'https://github.com/firebase/firebase-ios-sdk.git', +# Undo comment before release. +# :tag => 'Utilities-' + s.version.to_s + :tag => 'pre-5.3-' + s.version.to_s + } + s.ios.deployment_target = '6.0' + s.osx.deployment_target = '10.10' + s.tvos.deployment_target = '10.0' + + s.cocoapods_version = '>= 1.4.0' + s.prefix_header_file = false + + s.subspec 'Environment' do |es| + es.source_files = 'Firebase/Utilities/Environment/third_party/*.[mh]' + es.public_header_files = 'Firebase/Utilities/Environment/third_party/*.h' + es.private_header_files = 'Firebase/Utilities/Environment/third_party/*.h' + end + + s.subspec 'Logger' do |ls| + ls.source_files = 'Firebase/Utilities/Logger/**/*.[mh]' + ls.public_header_files = 'Firebase/Utilities/Logger/Private/*.h', 'Firebase/Utilities/Logger/Public/*.h' + ls.private_header_files = 'Firebase/Utilities/Logger/Private/*.h' + ls.dependency 'GoogleUtilities/Environment' + end + + s.subspec 'Network' do |ns| + ns.source_files = 'Firebase/Utilities/Network/**/*.[mh]' + ns.public_header_files = 'Firebase/Utilities/Network/Private/*.h' + ns.private_header_files = 'Firebase/Utilities/Network/Private/*.h' + ns.dependency 'GoogleUtilities/NSData+zlib' + ns.dependency 'GoogleUtilities/Logger' + ns.dependency 'GoogleUtilities/Reachability' + ns.frameworks = [ + 'Security' + ] + end + + s.subspec 'NSData+zlib' do |ns| + ns.source_files = 'Firebase/Utilities/NSData+zlib/*.[mh]' + ns.public_header_files = 'Firebase/Utilities/NSData+zlib/GULNSData+zlib.h' + ns.libraries = [ + 'z' + ] + end + + s.subspec 'Reachability' do |rs| + rs.source_files = 'Firebase/Utilities/Reachability/**/*.[mh]' + rs.public_header_files = 'Firebase/Utilities/Reachability/Private/*.h' + rs.private_header_files = 'Firebase/Utilities/Reachability/Private/*.h' + rs.frameworks = [ + 'SystemConfiguration' + ] + rs.dependency 'GoogleUtilities/Logger' + end +end diff --git a/cmake/FindGoogleUtilities.cmake b/cmake/FindGoogleUtilities.cmake new file mode 100644 index 0000000..37f7361 --- /dev/null +++ b/cmake/FindGoogleUtilities.cmake @@ -0,0 +1,56 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +find_library( + GoogleUtilities_LIBRARY + GoogleUtilities + PATHS ${FIREBASE_INSTALL_DIR}/Frameworks +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + GoogleUtilities + DEFAULT_MSG + GoogleUtilities_LIBRARY +) + +if(GoogleUtilities_FOUND) + # Emulate CocoaPods behavior which makes all headers available unqualified. + set( + GoogleUtilities_INCLUDE_DIRS + ${GoogleUtilities_LIBRARY}/Headers + ${GoogleUtilities_LIBRARY}/PrivateHeaders + ) + + set( + GoogleUtilities_LIBRARIES + ${GoogleUtilities_LIBRARY} + "-framework Foundation" + ) + + if(NOT TARGET GoogleUtilities) + # Add frameworks as INTERFACE libraries rather than IMPORTED so that + # framework behavior is preserved. + add_library(GoogleUtilities INTERFACE) + + set_property( + TARGET GoogleUtilities APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES ${GoogleUtilities_INCLUDE_DIRS} + ) + set_property( + TARGET GoogleUtilities APPEND PROPERTY + INTERFACE_LINK_LIBRARIES ${GoogleUtilities_LIBRARIES} + ) + endif() +endif(GoogleUtilities_FOUND) diff --git a/cmake/external/GoogleUtilities.cmake b/cmake/external/GoogleUtilities.cmake new file mode 100644 index 0000000..af90001 --- /dev/null +++ b/cmake/external/GoogleUtilities.cmake @@ -0,0 +1,23 @@ +# Copyright 2018 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include(xcodebuild) + +if(APPLE) + # GoogleUtilities is only available as a CocoaPod build. + xcodebuild(GoogleUtilities) +else() + # On non-Apple platforms, there's no way to build GoogleUtilities. + add_custom_target(GoogleUtilities) +endif() -- cgit v1.2.3