Push from command line

This commit is contained in:
Deniz Duezgoeren
2019-08-12 11:20:21 +02:00
parent 3a919dcb23
commit f1345eac14
512 changed files with 103288 additions and 1930 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
/*
* Copyright 2018 Google LLC
*
* 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 <Foundation/Foundation.h>
#import <GoogleUtilities/GULAppDelegateSwizzler.h>
#import <GoogleUtilities/GULMutableDictionary.h>
@class GULApplication;
NS_ASSUME_NONNULL_BEGIN
@interface GULAppDelegateSwizzler ()
/** ISA Swizzles the given appDelegate as the original app delegate would be.
*
* @param appDelegate The object that needs to be isa swizzled. This should conform to the
* application delegate protocol.
*/
+ (void)proxyAppDelegate:(id<GULApplicationDelegate>)appDelegate;
/** Returns a dictionary containing interceptor IDs mapped to a GULZeroingWeakContainer.
*
* @return A dictionary of the form {NSString : GULZeroingWeakContainer}, where the NSString is
* the interceptorID.
*/
+ (GULMutableDictionary *)interceptors;
/** Deletes all the registered interceptors. */
+ (void)clearInterceptors;
/** Resets the token that prevents the app delegate proxy from being isa swizzled multiple times. */
+ (void)resetProxyOriginalDelegateOnceToken;
/** Returns the original app delegate that was proxied.
*
* @return The original app delegate instance that was proxied.
*/
+ (id<GULApplicationDelegate>)originalDelegate;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,107 @@
/*
* Copyright 2018 Google LLC
*
* 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 <Foundation/Foundation.h>
#import <GoogleUtilities/GULApplication.h>
NS_ASSUME_NONNULL_BEGIN
typedef NSString *const GULAppDelegateInterceptorID;
/** This class contains methods that isa swizzle the app delegate. */
@interface GULAppDelegateSwizzler : NSProxy
/** Registers an app delegate interceptor whose methods will be invoked as they're invoked on the
* original app delegate.
*
* @param interceptor An instance of a class that conforms to the application delegate protocol.
* The interceptor is NOT retained.
* @return A unique GULAppDelegateInterceptorID if interceptor was successfully registered; nil
* if it fails.
*/
+ (nullable GULAppDelegateInterceptorID)registerAppDelegateInterceptor:
(id<GULApplicationDelegate>)interceptor;
/** Unregisters an interceptor with the given ID if it exists.
*
* @param interceptorID The object that was generated when the interceptor was registered.
*/
+ (void)unregisterAppDelegateInterceptorWithID:(GULAppDelegateInterceptorID)interceptorID;
/** This method ensures that the original app delegate has been proxied. Call this before
* registering your interceptor. This method is safe to call multiple times (but it only proxies
* the app delegate once).
*
* This method doesn't proxy APNS related methods:
* @code
* - application:didRegisterForRemoteNotificationsWithDeviceToken:
* - application:didFailToRegisterForRemoteNotificationsWithError:
* - application:didReceiveRemoteNotification:fetchCompletionHandler:
* - application:didReceiveRemoteNotification:
* @endcode
*
* To proxy these methods use +[GULAppDelegateSwizzler
* proxyOriginalDelegateIncludingAPNSMethods]. The methods have to be proxied separately to
* avoid potential warnings from Apple review about missing Push Notification Entitlement (e.g.
* https://github.com/firebase/firebase-ios-sdk/issues/2807)
*
* The method has no effect for extensions.
*
* @see proxyOriginalDelegateIncludingAPNSMethods
*/
+ (void)proxyOriginalDelegate;
/** This method ensures that the original app delegate has been proxied including APNS related
* methods. Call this before registering your interceptor. This method is safe to call multiple
* times (but it only proxies the app delegate once) or
* after +[GULAppDelegateSwizzler proxyOriginalDelegate]
*
* This method calls +[GULAppDelegateSwizzler proxyOriginalDelegate] under the hood.
* After calling this method the following App Delegate methods will be proxied in addition to
* the methods proxied by proxyOriginalDelegate:
* @code
* - application:didRegisterForRemoteNotificationsWithDeviceToken:
* - application:didFailToRegisterForRemoteNotificationsWithError:
* - application:didReceiveRemoteNotification:fetchCompletionHandler:
* - application:didReceiveRemoteNotification:
* @endcode
*
* The method has no effect for extensions.
*
* @see proxyOriginalDelegate
*/
+ (void)proxyOriginalDelegateIncludingAPNSMethods;
/** Indicates whether app delegate proxy is explicitly disabled or enabled. Enabled by default.
*
* @return YES if AppDelegateProxy is Enabled, NO otherwise.
*/
+ (BOOL)isAppDelegateProxyEnabled;
/** Returns the current sharedApplication.
*
* @return the current application instance if in an app, or nil if in extension or if it doesn't
* exist.
*/
+ (nullable GULApplication *)sharedApplication;
/** Do not initialize this class. */
- (instancetype)init NS_UNAVAILABLE;
NS_ASSUME_NONNULL_END
@end
@@ -0,0 +1,39 @@
/*
* Copyright 2019 Google LLC
*
* 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 <Foundation/Foundation.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
#define GULApplication UIApplication
#define GULApplicationDelegate UIApplicationDelegate
#define GULUserActivityRestoring UIUserActivityRestoring
static NSString *const kGULApplicationClassName = @"UIApplication";
#elif TARGET_OS_OSX
#import <AppKit/AppKit.h>
#define GULApplication NSApplication
#define GULApplicationDelegate NSApplicationDelegate
#define GULUserActivityRestoring NSUserActivityRestoring
static NSString *const kGULApplicationClassName = @"NSApplication";
#endif
@@ -0,0 +1,39 @@
/*
* Copyright 2018 Google LLC
*
* 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 <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, GULSwizzlerMessageCode) {
// App Delegate Swizzling.
kGULSwizzlerMessageCodeAppDelegateSwizzling000 = 1000, // I-SWZ001000
kGULSwizzlerMessageCodeAppDelegateSwizzling001 = 1001, // I-SWZ001001
kGULSwizzlerMessageCodeAppDelegateSwizzling002 = 1002, // I-SWZ001002
kGULSwizzlerMessageCodeAppDelegateSwizzling003 = 1003, // I-SWZ001003
kGULSwizzlerMessageCodeAppDelegateSwizzling004 = 1004, // I-SWZ001004
kGULSwizzlerMessageCodeAppDelegateSwizzling005 = 1005, // I-SWZ001005
kGULSwizzlerMessageCodeAppDelegateSwizzling006 = 1006, // I-SWZ001006
kGULSwizzlerMessageCodeAppDelegateSwizzling007 = 1007, // I-SWZ001007
kGULSwizzlerMessageCodeAppDelegateSwizzling008 = 1008, // I-SWZ001008
kGULSwizzlerMessageCodeAppDelegateSwizzling009 = 1009, // I-SWZ001009
kGULSwizzlerMessageCodeAppDelegateSwizzling010 = 1010, // I-SWZ001010
kGULSwizzlerMessageCodeAppDelegateSwizzling011 = 1011, // I-SWZ001011
kGULSwizzlerMessageCodeAppDelegateSwizzling012 = 1012, // I-SWZ001012
kGULSwizzlerMessageCodeAppDelegateSwizzling013 = 1013, // I-SWZ001013
kGULSwizzlerMessageCodeAppDelegateSwizzlingInvalidAppDelegate = 1014, // I-SWZ001014
// Method Swizzling.
kGULSwizzlerMessageCodeMethodSwizzling000 = 2000, // I-SWZ002000
};
@@ -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 <Foundation/Foundation.h>
@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;
/// @return Returns @YES when is run on iOS version greater or equal to 7.0
+ (BOOL)isIOS7OrHigher;
@end
@@ -0,0 +1,262 @@
// 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 <Foundation/Foundation.h>
#import <dlfcn.h>
#import <mach-o/dyld.h>
#import <sys/utsname.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif
/// 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 <landon@landonf.org>
/// 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 <a href="http://iphonedevwiki.net/index.php/Crack_prevention">iPhone Dev Wiki
/// Crack Prevention</a>:
/// 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 <a href="http://landonf.org/2009/02/index.html">Landon Fuller's blog</a>
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;
}
// The #else is for pre Xcode 9 where @available is not yet implemented.
#if __has_builtin(__builtin_available)
if (@available(iOS 7.0, *)) {
#else
if ([[UIDevice currentDevice].systemVersion integerValue] >= 7) {
#endif
NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL;
NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent;
return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName];
}
return NO;
}
+ (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 {
#if TARGET_OS_IOS
return [UIDevice currentDevice].systemVersion;
#elif TARGET_OS_OSX || TARGET_OS_TV
// 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;
#endif
}
+ (BOOL)isAppExtension {
#if TARGET_OS_IOS || TARGET_OS_TV
// Documented by <a href="https://goo.gl/RRB2Up">Apple</a>
BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
return appExtension;
#elif TARGET_OS_OSX
return NO;
#endif
}
+ (BOOL)isIOS7OrHigher {
#if __has_builtin(__builtin_available)
if (@available(iOS 7.0, *)) {
#else
if ([[UIDevice currentDevice].systemVersion integerValue] >= 7) {
#endif
return YES;
}
return NO;
}
@end
+209
View File
@@ -0,0 +1,209 @@
// 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 <asl.h>
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import <GoogleUtilities/GULLoggerLevel.h>
/// 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(void) {
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
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations" // asl is deprecated
// Initialize the ASL client handle.
sGULLoggerClient = asl_open(NULL, kGULLoggerASLClientFacilityName, aslOptions);
sGULLoggerMaximumLevel = GULLoggerLevelNotice;
// Set the filter used by system/device log. Initialize in default mode.
asl_set_filter(sGULLoggerClient, ASL_FILTER_MASK_UPTO(ASL_LEVEL_NOTICE));
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 GULLoggerEnableSTDERR(void) {
asl_add_log_file(sGULLoggerClient, STDERR_FILENO);
}
void GULLoggerForceDebug(void) {
// We should enable debug mode if we're not running from App Store.
if (![GULAppEnvironmentUtil isFromAppStore]) {
sGULLoggerDebugMode = YES;
GULSetLoggerLevel(GULLoggerLevelDebug);
}
}
__attribute__((no_sanitize("thread"))) void GULSetLoggerLevel(GULLoggerLevel loggerLevel) {
if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) {
GULLogError(kGULLoggerLogger, NO, @"I-COR000023", @"Invalid logger level, %ld",
(long)loggerLevel);
return;
}
GULLoggerInitializeASL();
// 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();
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();
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, (int)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] <Error> [{service}][I-COR000001] Configure blah failed.
* Calling GULLogDebug(kGULLoggerCore, @"I-COR000001", @"Configure succeed.") shows:
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [{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
@@ -0,0 +1,159 @@
/*
* 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 <Foundation/Foundation.h>
#import <GoogleUtilities/GULLoggerLevel.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The services used in the logger.
*/
typedef NSString *const GULLoggerService;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Initialize GULLogger.
*/
extern void GULLoggerInitializeASL(void);
/**
* Override log level to Debug.
*/
void GULLoggerForceDebug(void);
/**
* Turn on logging to STDERR.
*/
extern void GULLoggerEnableSTDERR(void);
/**
* 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
@@ -0,0 +1,37 @@
/*
* 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 <Foundation/Foundation.h>
/**
* 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_INFO. */
GULLoggerLevelInfo = 6,
/** Debug level, matches ASL_LEVEL_DEBUG. */
GULLoggerLevelDebug = 7,
/** Minimum log level. */
GULLoggerLevelMin = GULLoggerLevelError,
/** Maximum log level. */
GULLoggerLevelMax = GULLoggerLevelDebug
} NS_SWIFT_NAME(GoogleLoggerLevel);
@@ -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 <Foundation/Foundation.h>
/// 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
@@ -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 <zlib.h>
#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
@@ -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<NSCopying>)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<NSCopying>)key {
// The method this calls is already synchronized.
return [self objectForKey:key];
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)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
+389
View File
@@ -0,0 +1,389 @@
// 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 <GoogleUtilities/GULLogger.h>
#import <GoogleUtilities/GULNSData+zlib.h>
#import <GoogleUtilities/GULReachabilityChecker.h>
#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 () <GULReachabilityDelegate, GULNetworkLoggerDelegate>
@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<GULNetworkLoggerDelegate>)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
@@ -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 <Foundation/Foundation.h>
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]";
@@ -0,0 +1,740 @@
// 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 <Foundation/Foundation.h>
#import "Private/GULNetworkURLSession.h"
#import <GoogleUtilities/GULLogger.h>
#import "Private/GULMutableDictionary.h"
#import "Private/GULNetworkConstants.h"
#import "Private/GULNetworkMessageCode.h"
@interface GULNetworkURLSession () <NSURLSessionDelegate,
NSURLSessionTaskDelegate,
NSURLSessionDownloadDelegate>
@end
@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;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
/// The session configuration. NSURLSessionConfiguration' is only available on iOS 7.0 or newer.
NSURLSessionConfiguration *_sessionConfig;
/// The current NSURLSession.
NSURLSession *__weak _Nullable _URLSession;
#pragma clang diagnostic pop
/// 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<GULNetworkLoggerDelegate>)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.
- (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
completionHandler:(GULNetworkURLSessionCompletionHandler)handler
API_AVAILABLE(ios(7.0)) {
// 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 exclude 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];
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;
}
_URLSession = session;
// Save the session into memory.
[[self class] setSessionInFetcherMap:self forSessionID:_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.
- (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
completionHandler:(GULNetworkURLSessionCompletionHandler)handler
API_AVAILABLE(ios(7.0)) {
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;
}
_URLSession = session;
// Save the session into memory.
[[self class] setSessionInFetcherMap:self forSessionID:_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 API_AVAILABLE(ios(7.0)) {
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
API_AVAILABLE(ios(7.0)) {
[_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 API_AVAILABLE(ios(7.0)) {
// 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];
// This is called without checking the sessionID here since non-background sessions
// won't have an ID.
[session finishTasksAndInvalidate];
// Explicitly remove the session so it won't be reused. The weak map table should
// remove the session on deallocation, but dealloc may not happen immediately after
// calling `finishTasksAndInvalidate`.
NSString *sessionID = session.configuration.identifier;
[[self class] setSessionInFetcherMap:nil forSessionID:sessionID];
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler
API_AVAILABLE(ios(7.0)) {
// 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
API_AVAILABLE(ios(7.0)) {
#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+.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
#pragma clang diagnostic pop
} 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 {
GULNetworkURLSession *session = [self sessionFromFetcherMapForSessionID:sessionIdentifier];
if (!session && [sessionIdentifier hasPrefix:kGULNetworkBackgroundSessionConfigIDPrefix]) {
session = [[GULNetworkURLSession alloc] initWithNetworkLoggerDelegate:nil];
[session setSessionID:sessionIdentifier];
[self setSessionInFetcherMap:session forSessionID:sessionIdentifier];
}
return session;
}
/// Returns a map of the fetcher by session ID. Creates a map if it is not created.
/// When reading and writing from/to the session map, don't use this method directly.
/// To avoid thread safety issues, use one of the helper methods at the bottom of the
/// file: setSessionInFetcherMap:forSessionID:, sessionFromFetcherMapForSessionID:
+ (NSMapTable<NSString *, GULNetworkURLSession *> *)sessionIDToFetcherMap {
static NSMapTable *sessionIDToFetcherMap;
static dispatch_once_t sessionMapOnceToken;
dispatch_once(&sessionMapOnceToken, ^{
sessionIDToFetcherMap = [NSMapTable strongToWeakObjectsMapTable];
});
return sessionIDToFetcherMap;
}
+ (NSLock *)sessionIDToFetcherMapReadWriteLock {
static NSLock *lock;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lock = [[NSLock alloc] init];
});
return lock;
}
/// 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 API_AVAILABLE(ios(7.0)) {
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)setSessionInFetcherMap:(GULNetworkURLSession *)session forSessionID:(NSString *)sessionID {
[[self sessionIDToFetcherMapReadWriteLock] lock];
GULNetworkURLSession *existingSession =
[[[self class] sessionIDToFetcherMap] objectForKey:sessionID];
if (existingSession) {
if (session) {
NSString *message = [NSString stringWithFormat:@"Discarding session: %@", existingSession];
[existingSession->_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelInfo
messageCode:kGULNetworkMessageCodeURLSession019
message:message];
}
[existingSession->_URLSession finishTasksAndInvalidate];
}
if (session) {
[[[self class] sessionIDToFetcherMap] setObject:session forKey:sessionID];
} else {
[[[self class] sessionIDToFetcherMap] removeObjectForKey:sessionID];
}
[[self sessionIDToFetcherMapReadWriteLock] unlock];
}
+ (nullable GULNetworkURLSession *)sessionFromFetcherMapForSessionID:(NSString *)sessionID {
[[self sessionIDToFetcherMapReadWriteLock] lock];
GULNetworkURLSession *session = [[[self class] sessionIDToFetcherMap] objectForKey:sessionID];
[[self sessionIDToFetcherMapReadWriteLock] unlock];
return session;
}
- (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);
});
}
}
// Always use the request parameters even if the default session configuration is more restrictive.
- (void)populateSessionConfig:(NSURLSessionConfiguration *)sessionConfig
withRequest:(NSURLRequest *)request API_AVAILABLE(ios(7.0)) {
sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields;
sessionConfig.timeoutIntervalForRequest = request.timeoutInterval;
sessionConfig.timeoutIntervalForResource = request.timeoutInterval;
sessionConfig.requestCachePolicy = request.cachePolicy;
}
@end
@@ -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 <Foundation/Foundation.h>
/// 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<NSCopying>)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<NSCopying>)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<NSCopying>)key;
/// Returns the immutable dictionary.
- (NSDictionary *)dictionary;
@end
@@ -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 <Foundation/Foundation.h>
#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<GULNetworkReachabilityDelegate> reachabilityDelegate;
/// An optional delegate that can be used to log messages, warnings or errors that occur in the
/// network operations.
@property(nonatomic, weak) id<GULNetworkLoggerDelegate> 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
@@ -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 <Foundation/Foundation.h>
#import <GoogleUtilities/GULLogger.h>
/// 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;
@@ -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 <Foundation/Foundation.h>
#import <GoogleUtilities/GULLoggerLevel.h>
#import "GULNetworkMessageCode.h"
/// The log levels used by GULNetworkLogger.
typedef NS_ENUM(NSInteger, GULNetworkLogLevel) {
kGULNetworkLogLevelError = GULLoggerLevelError,
kGULNetworkLogLevelWarning = GULLoggerLevelWarning,
kGULNetworkLogLevelInfo = GULLoggerLevelInfo,
kGULNetworkLogLevelDebug = GULLoggerLevelDebug,
};
@protocol GULNetworkLoggerDelegate <NSObject>
@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
@@ -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 <Foundation/Foundation.h>
// 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
kGULNetworkMessageCodeURLSession019 = 901019, // I-NET901019
};
@@ -0,0 +1,62 @@
/*
* 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 <Foundation/Foundation.h>
#import "GULNetworkLoggerProtocol.h"
NS_ASSUME_NONNULL_BEGIN
typedef void (^GULNetworkCompletionHandler)(NSHTTPURLResponse *_Nullable response,
NSData *_Nullable data,
NSError *_Nullable error);
typedef void (^GULNetworkURLSessionCompletionHandler)(NSHTTPURLResponse *_Nullable response,
NSData *_Nullable data,
NSString *sessionID,
NSError *_Nullable 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, nullable) id<GULNetworkLoggerDelegate> 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:
(nullable id<GULNetworkLoggerDelegate>)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.
- (nullable 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.
- (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
completionHandler:(GULNetworkURLSessionCompletionHandler)handler;
NS_ASSUME_NONNULL_END
@end
@@ -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 <GoogleUtilities/GULReachabilityChecker.h>
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
@@ -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 <Foundation/Foundation.h>
#import "GULReachabilityChecker+Internal.h"
#import "Private/GULReachabilityChecker.h"
#import "Private/GULReachabilityMessageCode.h"
#import <GoogleUtilities/GULLogger.h>
#import <GoogleUtilities/GULReachabilityChecker.h>
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<GULReachabilityDelegate>)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<GULReachabilityDelegate>)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];
}
}
@@ -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 <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
/// 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<GULReachabilityDelegate> 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<GULReachabilityDelegate>)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
@@ -0,0 +1,29 @@
/*
* 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 <Foundation/Foundation.h>
// 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
};
@@ -0,0 +1,235 @@
// 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/GULUserDefaults.h"
#import <GoogleUtilities/GULLogger.h>
NS_ASSUME_NONNULL_BEGIN
static NSTimeInterval const kGULSynchronizeInterval = 1.0;
static NSString *const kGULLogFormat = @"I-GUL%06ld";
static GULLoggerService kGULLogUserDefaultsService = @"[GoogleUtilities/UserDefaults]";
typedef NS_ENUM(NSInteger, GULUDMessageCode) {
GULUDMessageCodeInvalidKeyGet = 1,
GULUDMessageCodeInvalidKeySet = 2,
GULUDMessageCodeInvalidObjectSet = 3,
GULUDMessageCodeSynchronizeFailed = 4,
};
@interface GULUserDefaults ()
/// Equivalent to the suite name for NSUserDefaults.
@property(readonly) CFStringRef appNameRef;
@property(atomic) BOOL isPreferenceFileExcluded;
@end
@implementation GULUserDefaults {
// The application name is the same with the suite name of the NSUserDefaults, and it is used for
// preferences.
CFStringRef _appNameRef;
}
+ (GULUserDefaults *)standardUserDefaults {
static GULUserDefaults *standardUserDefaults;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
standardUserDefaults = [[GULUserDefaults alloc] init];
});
return standardUserDefaults;
}
- (instancetype)init {
return [self initWithSuiteName:nil];
}
- (instancetype)initWithSuiteName:(nullable NSString *)suiteName {
self = [super init];
NSString *name = [suiteName copy];
if (self) {
// `kCFPreferencesCurrentApplication` maps to the same defaults database as
// `[NSUserDefaults standardUserDefaults]`.
_appNameRef =
name.length ? (__bridge_retained CFStringRef)name : kCFPreferencesCurrentApplication;
}
return self;
}
- (void)dealloc {
// If we're using a custom `_appNameRef` it needs to be released. If it's a constant, it shouldn't
// need to be released since we don't own it.
if (CFStringCompare(_appNameRef, kCFPreferencesCurrentApplication, 0) != kCFCompareEqualTo) {
CFRelease(_appNameRef);
}
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(synchronize)
object:nil];
}
- (nullable id)objectForKey:(NSString *)defaultName {
NSString *key = [defaultName copy];
if (![key isKindOfClass:[NSString class]] || !key.length) {
GULLogWarning(@"<GoogleUtilities>", NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeyGet],
@"Cannot get object for invalid user default key.");
return nil;
}
return (__bridge_transfer id)CFPreferencesCopyAppValue((__bridge CFStringRef)key, _appNameRef);
}
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName {
NSString *key = [defaultName copy];
if (![key isKindOfClass:[NSString class]] || !key.length) {
GULLogWarning(kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeySet],
@"Cannot set object for invalid user default key.");
return;
}
if (!value) {
CFPreferencesSetAppValue((__bridge CFStringRef)key, NULL, _appNameRef);
[self scheduleSynchronize];
return;
}
BOOL isAcceptableValue =
[value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]] ||
[value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]] ||
[value isKindOfClass:[NSDate class]] || [value isKindOfClass:[NSData class]];
if (!isAcceptableValue) {
GULLogWarning(kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidObjectSet],
@"Cannot set invalid object to user defaults. Must be a string, number, array, "
@"dictionary, date, or data. Value: %@",
value);
return;
}
CFPreferencesSetAppValue((__bridge CFStringRef)key, (__bridge CFStringRef)value, _appNameRef);
[self scheduleSynchronize];
}
- (void)removeObjectForKey:(NSString *)key {
[self setObject:nil forKey:key];
}
#pragma mark - Getters
- (NSInteger)integerForKey:(NSString *)defaultName {
NSNumber *object = [self objectForKey:defaultName];
return object.integerValue;
}
- (float)floatForKey:(NSString *)defaultName {
NSNumber *object = [self objectForKey:defaultName];
return object.floatValue;
}
- (double)doubleForKey:(NSString *)defaultName {
NSNumber *object = [self objectForKey:defaultName];
return object.doubleValue;
}
- (BOOL)boolForKey:(NSString *)defaultName {
NSNumber *object = [self objectForKey:defaultName];
return object.boolValue;
}
- (nullable NSString *)stringForKey:(NSString *)defaultName {
return [self objectForKey:defaultName];
}
- (nullable NSArray *)arrayForKey:(NSString *)defaultName {
return [self objectForKey:defaultName];
}
- (nullable NSDictionary<NSString *, id> *)dictionaryForKey:(NSString *)defaultName {
return [self objectForKey:defaultName];
}
#pragma mark - Setters
- (void)setInteger:(NSInteger)integer forKey:(NSString *)defaultName {
[self setObject:@(integer) forKey:defaultName];
}
- (void)setFloat:(float)value forKey:(NSString *)defaultName {
[self setObject:@(value) forKey:defaultName];
}
- (void)setDouble:(double)doubleNumber forKey:(NSString *)defaultName {
[self setObject:@(doubleNumber) forKey:defaultName];
}
- (void)setBool:(BOOL)boolValue forKey:(NSString *)defaultName {
[self setObject:@(boolValue) forKey:defaultName];
}
#pragma mark - Save data
- (void)synchronize {
if (!CFPreferencesAppSynchronize(_appNameRef)) {
GULLogError(kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeSynchronizeFailed],
@"Cannot synchronize user defaults to disk");
}
}
#pragma mark - Private methods
/// Removes all values from the search list entry specified by 'domainName', the current user, and
/// any host. The change is persistent. Equivalent to -removePersistentDomainForName: of
/// NSUserDefaults.
- (void)clearAllData {
// On macOS, using `kCFPreferencesCurrentHost` will not set all the keys necessary to match
// `NSUserDefaults`.
#if TARGET_OS_OSX
CFStringRef host = kCFPreferencesAnyHost;
#else
CFStringRef host = kCFPreferencesCurrentHost;
#endif // TARGET_OS_OSX
CFArrayRef keyList = CFPreferencesCopyKeyList(_appNameRef, kCFPreferencesCurrentUser, host);
if (!keyList) {
return;
}
CFPreferencesSetMultiple(NULL, keyList, _appNameRef, kCFPreferencesCurrentUser, host);
CFRelease(keyList);
[self scheduleSynchronize];
}
- (void)scheduleSynchronize {
// Synchronize data using a timer so that multiple set... calls can be coalesced under one
// synchronize.
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(synchronize)
object:nil];
// This method may be called on multiple queues (due to set... methods can be called on any queue)
// synchronize can be scheduled on different queues, so make sure that it does not crash. If this
// instance goes away, self will be released also, no one will retain it and the schedule won't be
// called.
[self performSelector:@selector(synchronize) withObject:nil afterDelay:kGULSynchronizeInterval];
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,110 @@
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A thread-safe user defaults that uses C functions from CFPreferences.h instead of
/// `NSUserDefaults`. This is to avoid sending an `NSNotification` when it's changed from a
/// background thread to avoid crashing. // TODO: Insert radar number here.
@interface GULUserDefaults : NSObject
/// A shared user defaults similar to +[NSUserDefaults standardUserDefaults] and accesses the same
/// data of the standardUserDefaults.
+ (GULUserDefaults *)standardUserDefaults;
/// Initializes preferences with a suite name that is the same with the NSUserDefaults' suite name.
/// Both of CFPreferences and NSUserDefaults share the same plist file so their data will exactly
/// the same.
///
/// @param suiteName The name of the suite of the user defaults.
- (instancetype)initWithSuiteName:(nullable NSString *)suiteName;
#pragma mark - Getters
/// Searches the receiver's search list for a default with the key 'defaultName' and return it. If
/// another process has changed defaults in the search list, NSUserDefaults will automatically
/// update to the latest values. If the key in question has been marked as ubiquitous via a Defaults
/// Configuration File, the latest value may not be immediately available, and the registered value
/// will be returned instead.
- (nullable id)objectForKey:(NSString *)defaultName;
/// Equivalent to -objectForKey:, except that it will return nil if the value is not an NSArray.
- (nullable NSArray *)arrayForKey:(NSString *)defaultName;
/// Equivalent to -objectForKey:, except that it will return nil if the value
/// is not an NSDictionary.
- (nullable NSDictionary<NSString *, id> *)dictionaryForKey:(NSString *)defaultName;
/// Equivalent to -objectForKey:, except that it will convert NSNumber values to their NSString
/// representation. If a non-string non-number value is found, nil will be returned.
- (nullable NSString *)stringForKey:(NSString *)defaultName;
/// Equivalent to -objectForKey:, except that it converts the returned value to an NSInteger. If the
/// value is an NSNumber, the result of -integerValue will be returned. If the value is an NSString,
/// it will be converted to NSInteger if possible. If the value is a boolean, it will be converted
/// to either 1 for YES or 0 for NO. If the value is absent or can't be converted to an integer, 0
/// will be returned.
- (NSInteger)integerForKey:(NSString *)defaultName;
/// Similar to -integerForKey:, except that it returns a float, and boolean values will not be
/// converted.
- (float)floatForKey:(NSString *)defaultName;
/// Similar to -integerForKey:, except that it returns a double, and boolean values will not be
/// converted.
- (double)doubleForKey:(NSString *)defaultName;
/// Equivalent to -objectForKey:, except that it converts the returned value to a BOOL. If the value
/// is an NSNumber, NO will be returned if the value is 0, YES otherwise. If the value is an
/// NSString, values of "YES" or "1" will return YES, and values of "NO", "0", or any other string
/// will return NO. If the value is absent or can't be converted to a BOOL, NO will be returned.
- (BOOL)boolForKey:(NSString *)defaultName;
#pragma mark - Setters
/// Immediately stores a value (or removes the value if `nil` is passed as the value) for the
/// provided key in the search list entry for the receiver's suite name in the current user and any
/// host, then asynchronously stores the value persistently, where it is made available to other
/// processes.
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName;
/// Equivalent to -setObject:forKey: except that the value is converted from a float to an NSNumber.
- (void)setFloat:(float)value forKey:(NSString *)defaultName;
/// Equivalent to -setObject:forKey: except that the value is converted from a double to an
/// NSNumber.
- (void)setDouble:(double)value forKey:(NSString *)defaultName;
/// Equivalent to -setObject:forKey: except that the value is converted from an NSInteger to an
/// NSNumber.
- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;
/// Equivalent to -setObject:forKey: except that the value is converted from a BOOL to an NSNumber.
- (void)setBool:(BOOL)value forKey:(NSString *)defaultName;
#pragma mark - Removing Defaults
/// Equivalent to -[... setObject:nil forKey:defaultName]
- (void)removeObjectForKey:(NSString *)defaultName;
#pragma mark - Save data
/// Blocks the calling thread until all in-progress set operations have completed.
- (void)synchronize;
@end
NS_ASSUME_NONNULL_END
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+223
View File
@@ -0,0 +1,223 @@
# Firebase iOS Open Source Development [![Build Status](https://travis-ci.org/firebase/firebase-ios-sdk.svg?branch=master)](https://travis-ci.org/firebase/firebase-ios-sdk)
This repository contains a subset of the Firebase iOS SDK source. It currently
includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore,
FirebaseFunctions, FirebaseInstanceID, FirebaseInAppMessaging,
FirebaseInAppMessagingDisplay, FirebaseMessaging and FirebaseStorage.
The repository also includes GoogleUtilities source. The
[GoogleUtilities](GoogleUtilities/README.md) pod is
a set of utilities used by Firebase and other Google products.
Firebase is an app development platform with tools to help you build, grow and
monetize your app. More information about Firebase can be found at
[https://firebase.google.com](https://firebase.google.com).
## Installation
See the three subsections for details about three different installation methods.
1. [Standard pod install](README.md#standard-pod-install)
1. [Installing from the GitHub repo](README.md#installing-from-github)
1. [Experimental Carthage](README.md#carthage-ios-only)
### Standard pod install
Go to
[https://firebase.google.com/docs/ios/setup](https://firebase.google.com/docs/ios/setup).
### Installing from GitHub
For releases starting with 5.0.0, the source for each release is also deployed
to CocoaPods master and available via standard
[CocoaPods Podfile syntax](https://guides.cocoapods.org/syntax/podfile.html#pod).
These instructions can be used to access the Firebase repo at other branches,
tags, or commits.
#### Background
See
[the Podfile Syntax Reference](https://guides.cocoapods.org/syntax/podfile.html#pod)
for instructions and options about overriding pod source locations.
#### Accessing Firebase Source Snapshots
All of the official releases are tagged in this repo and available via CocoaPods. To access a local
source snapshot or unreleased branch, use Podfile directives like the following:
To access FirebaseFirestore via a branch:
```
pod 'FirebaseCore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
pod 'FirebaseFirestore', :git => 'https://github.com/firebase/firebase-ios-sdk.git', :branch => 'master'
```
To access FirebaseMessaging via a checked out version of the firebase-ios-sdk repo do:
```
pod 'FirebaseCore', :path => '/path/to/firebase-ios-sdk'
pod 'FirebaseMessaging', :path => '/path/to/firebase-ios-sdk'
```
### Carthage (iOS only)
Instructions for the experimental Carthage distribution are at
[Carthage](Carthage.md).
### Rome
Instructions for installing binary frameworks via
[Rome](https://github.com/CocoaPods/Rome) are at [Rome](Rome.md).
## Development
To develop Firebase software in this repository, ensure that you have at least
the following software:
* Xcode 10.1 (or later)
* CocoaPods 1.7.2 (or later)
For the pod that you want to develop:
`pod gen Firebase{name here}.podspec --local-sources=./ --auto-open`
Firestore and Functions have self contained Xcode projects. See
[Firestore/README.md](Firestore/README.md) and
[Functions/README.md](Functions/README.md).
### Adding a New Firebase Pod
See [AddNewPod.md](AddNewPod.md).
### Code Formatting
To ensure that the code is formatted consistently, run the script
[./scripts/style.sh](https://github.com/firebase/firebase-ios-sdk/blob/master/scripts/style.sh)
before creating a PR.
Travis will verify that any code changes are done in a style compliant way. Install
`clang-format` and `swiftformat`.
These commands will get the right versions:
```
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/773cb75d360b58f32048f5964038d09825a507c8/Formula/clang-format.rb
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/3dfea1004e0736754bbf49673cca8aaed8a94089/Formula/swiftformat.rb
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
#### Viewing Code Coverage
First, make sure that [xcov](https://github.com/nakiostudio/xcov) is installed with `gem install xcov`.
After running the `AllUnitTests_iOS` scheme in Xcode, execute
`xcov --workspace Firebase.xcworkspace --scheme AllUnitTests_iOS --output_directory xcov_output`
at Example/ in the terminal. This will aggregate the coverage, and you can run `open xcov_output/index.html` to see the results.
### Running Sample Apps
In order to run the sample apps and integration tests, you'll need valid
`GoogleService-Info.plist` files for those samples. The Firebase Xcode project contains dummy plist
files without real values, but can be replaced with real plist files. To get your own
`GoogleService-Info.plist` files:
1. Go to the [Firebase Console](https://console.firebase.google.com/)
2. Create a new Firebase project, if you don't already have one
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
identifier (e.g. `com.google.Database-Example`)
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
(e.g. in [Example/Database/App/](Example/Database/App/));
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
special Apple capabilities, and you will have to change the sample app to use a unique bundle
identifier that you can control in your own Apple Developer account.
## Specific Component Instructions
See the sections below for any special instructions for those components.
### Firebase Auth
If you're doing specific Firebase Auth development, see
[the Auth Sample README](Example/Auth/README.md) for instructions about
building and running the FirebaseAuth pod along with various samples and tests.
### Firebase Database
To run the Database Integration tests, make your database authentication rules
[public](https://firebase.google.com/docs/database/security/quickstart).
### Firebase Storage
To run the Storage Integration tests, follow the instructions in
[FIRStorageIntegrationTests.m](Example/Storage/Tests/Integration/FIRStorageIntegrationTests.m).
#### Push Notifications
Push notifications can only be delivered to specially provisioned App IDs in the developer portal.
In order to actually test receiving push notifications, you will need to:
1. Change the bundle identifier of the sample app to something you own in your Apple Developer
account, and enable that App ID for push notifications.
2. You'll also need to
[upload your APNs Provider Authentication Key or certificate to the Firebase Console](https://firebase.google.com/docs/cloud-messaging/ios/certs)
at **Project Settings > Cloud Messaging > [Your Firebase App]**.
3. Ensure your iOS device is added to your Apple Developer portal as a test device.
#### iOS Simulator
The iOS Simulator cannot register for remote notifications, and will not receive push notifications.
In order to receive push notifications, you'll have to follow the steps above and run the app on a
physical device.
## Community Supported Efforts
We've seen an amazing amount of interest and contributions to improve the Firebase SDKs, and we are
very grateful! We'd like to empower as many developers as we can to be able to use Firebase and
participate in the Firebase community.
### macOS and tvOS
Thanks to contributions from the community, FirebaseAuth, FirebaseCore, FirebaseDatabase, FirebaseMessaging,
FirebaseFirestore, FirebaseFunctions and FirebaseStorage now compile, run unit tests, and work on
macOS and tvOS.
For tvOS, checkout the [Sample](Example/tvOSSample).
Keep in mind that macOS and tvOS are not officially supported by Firebase, and this repository is
actively developed primarily for iOS. While we can catch basic unit test issues with Travis, there
may be some changes where the SDK no longer works as expected on macOS or tvOS. If you encounter
this, please [file an issue](https://github.com/firebase/firebase-ios-sdk/issues).
Note that the Firebase pod is not available for macOS and tvOS.
To install, add a subset of the following to the Podfile:
```
pod 'FirebaseAuth'
pod 'FirebaseCore'
pod 'FirebaseDatabase'
pod 'FirebaseFirestore'
pod 'FirebaseFunctions'
pod 'FirebaseMessaging'
pod 'FirebaseStorage'
```
## Roadmap
See [Roadmap](ROADMAP.md) for more about the Firebase iOS SDK Open Source
plans and directions.
## Contributing
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
iOS SDK.
## License
The contents of this repository is licensed under the
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Your use of Firebase is governed by the
[Terms of Service for Firebase Services](https://firebase.google.com/terms/).