firebase log level

This commit is contained in:
oscarz
2024-08-29 18:25:13 +08:00
parent 8500300d18
commit 27c160beaf
1165 changed files with 122916 additions and 1 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,444 @@
// 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 <TargetConditionals.h>
#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h"
#import "GoogleUtilities/AppDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h"
#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h"
#import "GoogleUtilities/Common/GULLoggerCodes.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
#import <objc/runtime.h>
#if UISCENE_SUPPORTED
API_AVAILABLE(ios(13.0), tvos(13.0))
typedef void (*GULOpenURLContextsIMP)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
API_AVAILABLE(ios(13.0), tvos(13.0))
typedef void (^GULSceneDelegateInterceptorCallback)(id<UISceneDelegate>);
// The strings below are the keys for associated objects.
static char const *const kGULRealIMPBySelectorKey = "GUL_realIMPBySelector";
static char const *const kGULRealClassKey = "GUL_realClass";
#endif // UISCENE_SUPPORTED
static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/SceneDelegateSwizzler]";
// Since Firebase SDKs also use this for app delegate proxying, in order to not be a breaking change
// we disable App Delegate proxying when either of these two flags are set to NO.
/** Plist key that allows Firebase developers to disable App and Scene Delegate Proxying. */
static NSString *const kGULFirebaseSceneDelegateProxyEnabledPlistKey =
@"FirebaseAppDelegateProxyEnabled";
/** Plist key that allows developers not using Firebase to disable App and Scene Delegate Proxying.
*/
static NSString *const kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey =
@"GoogleUtilitiesAppDelegateProxyEnabled";
/** The prefix of the Scene Delegate. */
static NSString *const kGULSceneDelegatePrefix = @"GUL_";
/**
* This class is necessary to store the delegates in an NSArray without retaining them.
* [NSValue valueWithNonRetainedObject] also provides this functionality, but does not provide a
* zeroing pointer. This will cause EXC_BAD_ACCESS when trying to access the object after it is
* dealloced. Instead, this container stores a weak, zeroing reference to the object, which
* automatically is set to nil by the runtime when the object is dealloced.
*/
@interface GULSceneZeroingWeakContainer : NSObject
/** Stores a weak object. */
@property(nonatomic, weak) id object;
@end
@implementation GULSceneZeroingWeakContainer
@end
@implementation GULSceneDelegateSwizzler
#pragma mark - Public methods
+ (BOOL)isSceneDelegateProxyEnabled {
return [GULAppDelegateSwizzler isAppDelegateProxyEnabled];
}
+ (void)proxyOriginalSceneDelegate {
#if UISCENE_SUPPORTED
if ([GULAppEnvironmentUtil isAppExtension]) {
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (@available(iOS 13.0, tvOS 13.0, *)) {
if (![GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]) {
return;
}
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(handleSceneWillConnectToNotification:)
name:UISceneWillConnectNotification
object:nil];
}
});
#endif // UISCENE_SUPPORTED
}
#if UISCENE_SUPPORTED
+ (GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor:(id<UISceneDelegate>)interceptor {
NSAssert(interceptor, @"SceneDelegateProxy cannot add nil interceptor");
NSAssert([interceptor conformsToProtocol:@protocol(UISceneDelegate)],
@"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate");
if (!interceptor) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling000],
@"SceneDelegateProxy cannot add nil interceptor.");
return nil;
}
if (![interceptor conformsToProtocol:@protocol(UISceneDelegate)]) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling001],
@"SceneDelegateProxy interceptor does not conform to UIApplicationDelegate");
return nil;
}
// The ID should be the same given the same interceptor object.
NSString *interceptorID =
[NSString stringWithFormat:@"%@%p", kGULSceneDelegatePrefix, interceptor];
if (!interceptorID.length) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling002],
@"SceneDelegateProxy cannot create Interceptor ID.");
return nil;
}
GULSceneZeroingWeakContainer *weakObject = [[GULSceneZeroingWeakContainer alloc] init];
weakObject.object = interceptor;
[GULSceneDelegateSwizzler interceptors][interceptorID] = weakObject;
return interceptorID;
}
+ (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID {
NSAssert(interceptorID, @"SceneDelegateProxy cannot unregister nil interceptor ID.");
NSAssert(((NSString *)interceptorID).length != 0,
@"SceneDelegateProxy cannot unregister empty interceptor ID.");
if (!interceptorID) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling003],
@"SceneDelegateProxy cannot unregister empty interceptor ID.");
return;
}
GULSceneZeroingWeakContainer *weakContainer =
[GULSceneDelegateSwizzler interceptors][interceptorID];
if (!weakContainer.object) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling004],
@"SceneDelegateProxy cannot unregister interceptor that was not registered. "
"Interceptor ID %@",
interceptorID);
return;
}
[[GULSceneDelegateSwizzler interceptors] removeObjectForKey:interceptorID];
}
#pragma mark - Helper methods
+ (GULMutableDictionary *)interceptors {
static dispatch_once_t onceToken;
static GULMutableDictionary *sInterceptors;
dispatch_once(&onceToken, ^{
sInterceptors = [[GULMutableDictionary alloc] init];
});
return sInterceptors;
}
+ (void)clearInterceptors {
[[self interceptors] removeAllObjects];
}
+ (nullable NSValue *)originalImplementationForSelector:(SEL)selector object:(id)object {
NSDictionary *realImplementationBySelector =
objc_getAssociatedObject(object, &kGULRealIMPBySelectorKey);
return realImplementationBySelector[NSStringFromSelector(selector)];
}
+ (void)proxyDestinationSelector:(SEL)destinationSelector
implementationsFromSourceSelector:(SEL)sourceSelector
fromClass:(Class)sourceClass
toClass:(Class)destinationClass
realClass:(Class)realClass
storeDestinationImplementationTo:
(NSMutableDictionary<NSString *, NSValue *> *)destinationImplementationsBySelector {
[self addInstanceMethodWithDestinationSelector:destinationSelector
withImplementationFromSourceSelector:sourceSelector
fromClass:sourceClass
toClass:destinationClass];
IMP sourceImplementation =
[GULSceneDelegateSwizzler implementationOfMethodSelector:destinationSelector
fromClass:realClass];
NSValue *sourceImplementationPointer = [NSValue valueWithPointer:sourceImplementation];
NSString *destinationSelectorString = NSStringFromSelector(destinationSelector);
destinationImplementationsBySelector[destinationSelectorString] = sourceImplementationPointer;
}
/** Copies a method identified by the methodSelector from one class to the other. After this method
* is called, performing [toClassInstance methodSelector] will be similar to calling
* [fromClassInstance methodSelector]. This method does nothing if toClass already has a method
* identified by methodSelector.
*
* @param methodSelector The SEL that identifies both the method on the fromClass as well as the
* one on the toClass.
* @param fromClass The class from which a method is sourced.
* @param toClass The class to which the method is added. If the class already has a method with
* the same selector, this has no effect.
*/
+ (void)addInstanceMethodWithSelector:(SEL)methodSelector
fromClass:(Class)fromClass
toClass:(Class)toClass {
[self addInstanceMethodWithDestinationSelector:methodSelector
withImplementationFromSourceSelector:methodSelector
fromClass:fromClass
toClass:toClass];
}
/** Copies a method identified by the sourceSelector from the fromClass as a method for the
* destinationSelector on the toClass. After this method is called, performing
* [toClassInstance destinationSelector] will be similar to calling
* [fromClassInstance sourceSelector]. This method does nothing if toClass already has a method
* identified by destinationSelector.
*
* @param destinationSelector The SEL that identifies the method on the toClass.
* @param sourceSelector The SEL that identifies the method on the fromClass.
* @param fromClass The class from which a method is sourced.
* @param toClass The class to which the method is added. If the class already has a method with
* the same selector, this has no effect.
*/
+ (void)addInstanceMethodWithDestinationSelector:(SEL)destinationSelector
withImplementationFromSourceSelector:(SEL)sourceSelector
fromClass:(Class)fromClass
toClass:(Class)toClass {
Method method = class_getInstanceMethod(fromClass, sourceSelector);
IMP methodIMP = method_getImplementation(method);
const char *types = method_getTypeEncoding(method);
if (!class_addMethod(toClass, destinationSelector, methodIMP, types)) {
GULOSLogWarning(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeSceneDelegateSwizzling009],
@"Cannot copy method to destination selector %@ as it already exists",
NSStringFromSelector(destinationSelector));
}
}
/** Gets the IMP of the instance method on the class identified by the selector.
*
* @param selector The selector of which the IMP is to be fetched.
* @param aClass The class from which the IMP is to be fetched.
* @return The IMP of the instance method identified by selector and aClass.
*/
+ (IMP)implementationOfMethodSelector:(SEL)selector fromClass:(Class)aClass {
Method aMethod = class_getInstanceMethod(aClass, selector);
return method_getImplementation(aMethod);
}
/** Enumerates through all the interceptors and if they respond to a given selector, executes a
* GULSceneDelegateInterceptorCallback with the interceptor.
*
* @param methodSelector The SEL to check if an interceptor responds to.
* @param callback the GULSceneDelegateInterceptorCallback.
*/
+ (void)notifyInterceptorsWithMethodSelector:(SEL)methodSelector
callback:(GULSceneDelegateInterceptorCallback)callback
API_AVAILABLE(ios(13.0)) {
if (!callback) {
return;
}
NSDictionary *interceptors = [GULSceneDelegateSwizzler interceptors].dictionary;
[interceptors enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
GULSceneZeroingWeakContainer *interceptorContainer = obj;
id interceptor = interceptorContainer.object;
if (!interceptor) {
GULOSLogWarning(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString stringWithFormat:@"I-SWZ%06ld",
(long)kGULSwizzlerMessageCodeSceneDelegateSwizzling010],
@"SceneDelegateProxy cannot find interceptor with ID %@. Removing the interceptor.", key);
[[GULSceneDelegateSwizzler interceptors] removeObjectForKey:key];
return;
}
if ([interceptor respondsToSelector:methodSelector]) {
callback(interceptor);
}
}];
}
+ (void)handleSceneWillConnectToNotification:(NSNotification *)notification {
if (@available(iOS 13.0, tvOS 13.0, *)) {
if ([notification.object isKindOfClass:[UIScene class]]) {
UIScene *scene = (UIScene *)notification.object;
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:scene];
}
}
}
#pragma mark - [Donor Methods] UISceneDelegate URL handler
- (void)scene:(UIScene *)scene
openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts API_AVAILABLE(ios(13.0), tvos(13.0)) {
if (@available(iOS 13.0, tvOS 13.0, *)) {
SEL methodSelector = @selector(scene:openURLContexts:);
// Call the real implementation if the real Scene Delegate has any.
NSValue *openURLContextsIMPPointer =
[GULSceneDelegateSwizzler originalImplementationForSelector:methodSelector object:self];
GULOpenURLContextsIMP openURLContextsIMP = [openURLContextsIMPPointer pointerValue];
[GULSceneDelegateSwizzler
notifyInterceptorsWithMethodSelector:methodSelector
callback:^(id<UISceneDelegate> interceptor) {
if ([interceptor
conformsToProtocol:@protocol(UISceneDelegate)]) {
id<UISceneDelegate> sceneInterceptor =
(id<UISceneDelegate>)interceptor;
[sceneInterceptor scene:scene openURLContexts:URLContexts];
}
}];
if (openURLContextsIMP) {
openURLContextsIMP(self, methodSelector, scene, URLContexts);
}
}
}
+ (void)proxySceneDelegateIfNeeded:(UIScene *)scene {
Class realClass = [scene.delegate class];
NSString *className = NSStringFromClass(realClass);
// Skip proxying if failed to get the delegate class name for some reason (e.g. `delegate == nil`)
// or the class has a prefix of kGULAppDelegatePrefix, which means it has been proxied before.
if (className == nil || [className hasPrefix:kGULSceneDelegatePrefix]) {
return;
}
NSString *classNameWithPrefix = [kGULSceneDelegatePrefix stringByAppendingString:className];
NSString *newClassName =
[NSString stringWithFormat:@"%@-%@", classNameWithPrefix, [NSUUID UUID].UUIDString];
if (NSClassFromString(newClassName)) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)
kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
@"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class"
@": %@, subclass: %@",
className, newClassName);
return;
}
// Register the new class as subclass of the real one. Do not allocate more than the real class
// size.
Class sceneDelegateSubClass = objc_allocateClassPair(realClass, newClassName.UTF8String, 0);
if (sceneDelegateSubClass == Nil) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)
kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
@"Cannot create a proxy for Scene Delegate. Subclass already exists. Original Class"
@": %@, subclass: Nil",
className);
return;
}
NSMutableDictionary<NSString *, NSValue *> *realImplementationsBySelector =
[[NSMutableDictionary alloc] init];
// For scene:openURLContexts:
SEL openURLContextsSEL = @selector(scene:openURLContexts:);
[self proxyDestinationSelector:openURLContextsSEL
implementationsFromSourceSelector:openURLContextsSEL
fromClass:[GULSceneDelegateSwizzler class]
toClass:sceneDelegateSubClass
realClass:realClass
storeDestinationImplementationTo:realImplementationsBySelector];
// Store original implementations to a fake property of the original delegate.
objc_setAssociatedObject(scene.delegate, &kGULRealIMPBySelectorKey,
[realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(scene.delegate, &kGULRealClassKey, realClass,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// The subclass size has to be exactly the same size with the original class size. The subclass
// cannot have more ivars/properties than its superclass since it will cause an offset in memory
// that can lead to overwriting the isa of an object in the next frame.
if (class_getInstanceSize(realClass) != class_getInstanceSize(sceneDelegateSubClass)) {
GULOSLogError(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)
kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
@"Cannot create subclass of Scene Delegate, because the created subclass is not the "
@"same size. %@",
className);
NSAssert(NO, @"Classes must be the same size to swizzle isa");
return;
}
// Make the newly created class to be the subclass of the real Scene Delegate class.
objc_registerClassPair(sceneDelegateSubClass);
if (object_setClass(scene.delegate, sceneDelegateSubClass)) {
GULOSLogDebug(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld",
(long)
kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate],
@"Successfully created Scene Delegate Proxy automatically. To disable the "
@"proxy, set the flag %@ to NO (Boolean) in the Info.plist",
[GULSceneDelegateSwizzler correctSceneDelegateProxyKey]);
}
}
+ (NSString *)correctSceneDelegateProxyKey {
return NSClassFromString(@"FIRCore") ? kGULFirebaseSceneDelegateProxyEnabledPlistKey
: kGULGoogleUtilitiesSceneDelegateProxyEnabledPlistKey;
}
#endif // UISCENE_SUPPORTED
@end

View File

@ -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/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h"
#import "GoogleUtilities/Network/Public/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

View File

@ -0,0 +1,48 @@
/*
* 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>
#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
NS_ASSUME_NONNULL_BEGIN
@interface GULSceneDelegateSwizzler ()
#if UISCENE_SUPPORTED
/** 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;
/** ISA Swizzles the given appDelegate as the original app delegate would be.
*
* @param scene The scene whose delegate needs to be isa swizzled. This should conform to the
* scene delegate protocol.
*/
+ (void)proxySceneDelegateIfNeeded:(UIScene *)scene API_AVAILABLE(ios(13.0), tvos(13.0));
#endif // UISCENE_SUPPORTED
@end
NS_ASSUME_NONNULL_END

View File

@ -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 "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

View File

@ -0,0 +1,50 @@
/*
* 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 || TARGET_OS_VISION
#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";
#elif TARGET_OS_WATCH
#import <WatchKit/WatchKit.h>
// We match the according watchOS API but swizzling should not work in watch
#define GULApplication WKExtension
#define GULApplicationDelegate WKExtensionDelegate
#define GULUserActivityRestoring NSUserActivityRestoring
static NSString *const kGULApplicationClassName = @"WKExtension";
#endif

View File

@ -0,0 +1,76 @@
/*
* 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>
#import <TargetConditionals.h>
#if __has_include(<UIKit/UIKit.h>)
#import <UIKit/UIKit.h>
#endif
#if TARGET_OS_IOS || TARGET_OS_TV
#define UISCENE_SUPPORTED 1
#endif
NS_ASSUME_NONNULL_BEGIN
typedef NSString *const GULSceneDelegateInterceptorID;
/** This class contains methods that isa swizzle the scene delegate. */
@interface GULSceneDelegateSwizzler : NSProxy
#if UISCENE_SUPPORTED
/** Registers a scene delegate interceptor whose methods will be invoked as they're invoked on the
* original scene delegate.
*
* @param interceptor An instance of a class that conforms to the application delegate protocol.
* The interceptor is NOT retained.
* @return A unique GULSceneDelegateInterceptorID if interceptor was successfully registered; nil
* if it fails.
*/
+ (nullable GULSceneDelegateInterceptorID)registerSceneDelegateInterceptor:
(id<UISceneDelegate>)interceptor API_AVAILABLE(ios(13.0), tvos(13.0));
/** Unregisters an interceptor with the given ID if it exists.
*
* @param interceptorID The object that was generated when the interceptor was registered.
*/
+ (void)unregisterSceneDelegateInterceptorWithID:(GULSceneDelegateInterceptorID)interceptorID
API_AVAILABLE(ios(13.0), tvos(13.0));
/** Do not initialize this class. */
- (instancetype)init NS_UNAVAILABLE;
#endif // UISCENE_SUPPORTED
/** This method ensures that the original scene delegate has been proxied. Call this before
* registering your interceptor. This method is safe to call multiple times (but it only proxies
* the scene delegate once).
*
* The method has no effect for extensions.
*/
+ (void)proxyOriginalSceneDelegate;
/** Indicates whether scene delegate proxy is explicitly disabled or enabled. Enabled by default.
*
* @return YES if SceneDelegateProxy is Enabled, NO otherwise.
*/
+ (BOOL)isSceneDelegateProxyEnabled;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,56 @@
/*
* 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
// Scene Delegate Swizzling.
kGULSwizzlerMessageCodeSceneDelegateSwizzling000 = 1100, // I-SWZ001100
kGULSwizzlerMessageCodeSceneDelegateSwizzling001 = 1101, // I-SWZ001101
kGULSwizzlerMessageCodeSceneDelegateSwizzling002 = 1102, // I-SWZ001102
kGULSwizzlerMessageCodeSceneDelegateSwizzling003 = 1103, // I-SWZ001103
kGULSwizzlerMessageCodeSceneDelegateSwizzling004 = 1104, // I-SWZ001104
kGULSwizzlerMessageCodeSceneDelegateSwizzling005 = 1105, // I-SWZ001105
kGULSwizzlerMessageCodeSceneDelegateSwizzling006 = 1106, // I-SWZ001106
kGULSwizzlerMessageCodeSceneDelegateSwizzling007 = 1107, // I-SWZ001107
kGULSwizzlerMessageCodeSceneDelegateSwizzling008 = 1108, // I-SWZ001108
kGULSwizzlerMessageCodeSceneDelegateSwizzling009 = 1109, // I-SWZ001109
kGULSwizzlerMessageCodeSceneDelegateSwizzling010 = 1110, // I-SWZ001110
kGULSwizzlerMessageCodeSceneDelegateSwizzling011 = 1111, // I-SWZ001111
kGULSwizzlerMessageCodeSceneDelegateSwizzling012 = 1112, // I-SWZ001112
kGULSwizzlerMessageCodeSceneDelegateSwizzling013 = 1113, // I-SWZ001113
kGULSwizzlerMessageCodeSceneDelegateSwizzlingInvalidSceneDelegate = 1114, // I-SWZ001114
// Method Swizzling.
kGULSwizzlerMessageCodeMethodSwizzling000 = 2000, // I-SWZ002000
};

View File

@ -0,0 +1,281 @@
// 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/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <sys/sysctl.h>
#import <sys/utsname.h>
#import "third_party/IsAppEncrypted/Public/IsAppEncrypted.h"
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif // TARGET_OS_IOS
@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";
static BOOL HasSCInfoFolder(void) {
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
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(void) {
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
return [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"].length > 0;
#elif TARGET_OS_OSX
return NO;
#endif
}
+ (BOOL)isFromAppStore {
static dispatch_once_t isEncryptedOnce;
static BOOL isEncrypted = NO;
dispatch_once(&isEncryptedOnce, ^{
isEncrypted = IsAppEncrypted();
});
if ([GULAppEnvironmentUtil isSimulator]) {
return NO;
}
// If an app contain the sandboxReceipt file, it means its coming from TestFlight
// This must be checked before the SCInfo Folder check below since TestFlight apps may
// also have an SCInfo folder.
if ([GULAppEnvironmentUtil isAppStoreReceiptSandbox]) {
return NO;
}
if (HasSCInfoFolder()) {
// When iTunes downloads a .ipa, it also gets a customized .sinf file which is added to the
// main SC_Info directory.
return YES;
}
// For iOS >= 8.0, iTunesMetadata.plist is moved outside of the sandbox. Any attempt to read
// the iTunesMetadata.plist outside of the sandbox will be rejected by Apple.
// If the app does not contain the embedded.mobileprovision which is stripped out by Apple when
// the app is submitted to store, then it is highly likely that it is from Apple Store.
return isEncrypted && !HasEmbeddedMobileProvision();
}
+ (BOOL)isAppStoreReceiptSandbox {
// Since checking the App Store's receipt URL can be memory intensive, check the option in the
// Info.plist if developers opted out of this check.
id enableSandboxCheck =
[[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRAppStoreReceiptURLCheckEnabledKey];
if (enableSandboxCheck && [enableSandboxCheck isKindOfClass:[NSNumber class]] &&
![enableSandboxCheck boolValue]) {
return NO;
}
NSURL *appStoreReceiptURL = [NSBundle mainBundle].appStoreReceiptURL;
NSString *appStoreReceiptFileName = appStoreReceiptURL.lastPathComponent;
return [appStoreReceiptFileName isEqualToString:kFIRAIdentitySandboxReceiptFileName];
}
+ (BOOL)isSimulator {
#if TARGET_OS_SIMULATOR
return YES;
#elif TARGET_OS_MACCATALYST
return NO;
#elif 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
return NO;
}
+ (NSString *)getSysctlEntry:(const char *)sysctlKey {
static NSString *entryValue;
size_t size;
sysctlbyname(sysctlKey, NULL, &size, NULL, 0);
if (size > 0) {
char *entryValueCStr = malloc(size);
sysctlbyname(sysctlKey, entryValueCStr, &size, NULL, 0);
entryValue = [NSString stringWithCString:entryValueCStr encoding:NSUTF8StringEncoding];
free(entryValueCStr);
return entryValue;
} else {
return nil;
}
}
+ (NSString *)deviceModel {
static dispatch_once_t once;
static NSString *deviceModel;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
dispatch_once(&once, ^{
// The `uname` function only returns x86_64 for Macs. Use `sysctlbyname` instead, but fall back
// to the `uname` function if it fails.
deviceModel = [GULAppEnvironmentUtil getSysctlEntry:"hw.model"];
if (deviceModel.length == 0) {
struct utsname systemInfo;
if (uname(&systemInfo) == 0) {
deviceModel = [NSString stringWithUTF8String:systemInfo.machine];
}
}
});
#else
dispatch_once(&once, ^{
struct utsname systemInfo;
if (uname(&systemInfo) == 0) {
deviceModel = [NSString stringWithUTF8String:systemInfo.machine];
}
});
#endif // TARGET_OS_OSX || TARGET_OS_MACCATALYST
return deviceModel;
}
+ (NSString *)deviceSimulatorModel {
static dispatch_once_t once;
static NSString *model = nil;
dispatch_once(&once, ^{
#if TARGET_OS_SIMULATOR
#if TARGET_OS_WATCH
model = @"watchOS Simulator";
#elif TARGET_OS_TV
model = @"tvOS Simulator";
#elif TARGET_OS_VISION
model = @"visionOS Simulator";
#elif TARGET_OS_IOS
switch ([[UIDevice currentDevice] userInterfaceIdiom]) {
case UIUserInterfaceIdiomPhone:
model = @"iOS Simulator (iPhone)";
break;
case UIUserInterfaceIdiomPad:
model = @"iOS Simulator (iPad)";
break;
default:
model = @"iOS Simulator (Unknown)";
break;
}
#endif
#elif TARGET_OS_EMBEDDED
model = [GULAppEnvironmentUtil getSysctlEntry:"hw.machine"];
#else
model = [GULAppEnvironmentUtil getSysctlEntry:"hw.model"];
#endif
});
return model;
}
+ (NSString *)systemVersion {
#if TARGET_OS_IOS
return [UIDevice currentDevice].systemVersion;
#elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH || TARGET_OS_VISION
// 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 || TARGET_OS_WATCH
// 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
}
+ (NSString *)applePlatform {
NSString *applePlatform = @"unknown";
// When a Catalyst app is run on macOS then both `TARGET_OS_MACCATALYST` and `TARGET_OS_IOS` are
// `true`, which means the condition list is order-sensitive.
#if TARGET_OS_MACCATALYST
applePlatform = @"maccatalyst";
#elif TARGET_OS_IOS
if (@available(iOS 14.0, *)) {
// Early iOS 14 betas do not include isiOSAppOnMac (#6969)
applePlatform = ([[NSProcessInfo processInfo] respondsToSelector:@selector(isiOSAppOnMac)] &&
[NSProcessInfo processInfo].isiOSAppOnMac)
? @"ios_on_mac"
: @"ios";
} else {
applePlatform = @"ios";
}
#elif TARGET_OS_TV
applePlatform = @"tvos";
#elif TARGET_OS_OSX
applePlatform = @"macos";
#elif TARGET_OS_WATCH
applePlatform = @"watchos";
#elif TARGET_OS_VISION
applePlatform = @"visionos";
#endif // TARGET_OS_MACCATALYST
return applePlatform;
}
+ (NSString *)appleDevicePlatform {
NSString *firebasePlatform = [GULAppEnvironmentUtil applePlatform];
#if TARGET_OS_IOS
// This check is necessary because iOS-only apps running on iPad
// will report UIUserInterfaceIdiomPhone via UI_USER_INTERFACE_IDIOM().
if ([firebasePlatform isEqualToString:@"ios"] &&
([[UIDevice currentDevice].model.lowercaseString containsString:@"ipad"] ||
[[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)) {
return @"ipados";
}
#endif
return firebasePlatform;
}
+ (NSString *)deploymentType {
#if SWIFT_PACKAGE
NSString *deploymentType = @"swiftpm";
#elif FIREBASE_BUILD_CARTHAGE
NSString *deploymentType = @"carthage";
#elif FIREBASE_BUILD_ZIP_FILE
NSString *deploymentType = @"zip";
#elif COCOAPODS
NSString *deploymentType = @"cocoapods";
#else
NSString *deploymentType = @"unknown";
#endif
return deploymentType;
}
@end

View File

@ -0,0 +1,80 @@
// Copyright 2022 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 "GoogleUtilities/Environment/Public/GoogleUtilities/GULNetworkInfo.h"
#import <Foundation/Foundation.h>
#import <TargetConditionals.h>
#if __has_include("CoreTelephony/CTTelephonyNetworkInfo.h") && !TARGET_OS_MACCATALYST && \
!TARGET_OS_OSX && !TARGET_OS_TV && !TARGET_OS_WATCH
#define TARGET_HAS_MOBILE_CONNECTIVITY
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <SystemConfiguration/SystemConfiguration.h>
#endif
@implementation GULNetworkInfo
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
+ (CTTelephonyNetworkInfo *)getNetworkInfo {
static CTTelephonyNetworkInfo *networkInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkInfo = [[CTTelephonyNetworkInfo alloc] init];
});
return networkInfo;
}
#endif
+ (GULNetworkType)getNetworkType {
GULNetworkType networkType = GULNetworkTypeNone;
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
static SCNetworkReachabilityRef reachabilityRef = 0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
reachabilityRef = SCNetworkReachabilityCreateWithName(kCFAllocatorSystemDefault, "google.com");
});
if (!reachabilityRef) {
return GULNetworkTypeNone;
}
SCNetworkReachabilityFlags reachabilityFlags = 0;
SCNetworkReachabilityGetFlags(reachabilityRef, &reachabilityFlags);
// Parse the network flags to set the network type.
if (reachabilityFlags & kSCNetworkReachabilityFlagsReachable) {
if (reachabilityFlags & kSCNetworkReachabilityFlagsIsWWAN) {
networkType = GULNetworkTypeMobile;
} else {
networkType = GULNetworkTypeWIFI;
}
}
#endif
return networkType;
}
+ (NSString *)getNetworkRadioType {
#ifdef TARGET_HAS_MOBILE_CONNECTIVITY
CTTelephonyNetworkInfo *networkInfo = [GULNetworkInfo getNetworkInfo];
if (networkInfo.serviceCurrentRadioAccessTechnology.count) {
return networkInfo.serviceCurrentRadioAccessTechnology.allValues[0] ?: @"";
}
#endif
return @"";
}
@end

View File

@ -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>
NS_ASSUME_NONNULL_BEGIN
@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.
+ (nullable NSString *)deviceModel;
/// The current device model, with simulator-specific values. Returns an empty string if device
/// model cannot be retrieved.
+ (nullable NSString *)deviceSimulatorModel;
/// 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 An Apple platform. Possible values "ios", "tvos", "macos", "watchos", "maccatalyst", and
/// "visionos".
+ (NSString *)applePlatform;
/// @return An Apple Device platform. Same possible values as `applePlatform`, with the addition of
/// "ipados".
+ (NSString *)appleDevicePlatform;
/// @return The way the library was added to the app, e.g. "swiftpm", "cocoapods", etc.
+ (NSString *)deploymentType;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,84 @@
/*
* Copyright 2019 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
/// The class provides a convenient, multiplatform abstraction of the Keychain.
///
/// When using this API on macOS, the corresponding target must be signed with a provisioning
/// profile that has the Keychain Sharing capability enabled.
@interface GULKeychainStorage : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Initializes the keychain storage with Keychain Service name.
* @param service A Keychain Service name that will be used to store and retrieve objects. See also
* `kSecAttrService`.
*/
- (instancetype)initWithService:(NSString *)service;
/// Get an object by key.
/// @param key The key.
/// @param objectClass The expected object class required by `NSSecureCoding`.
/// @param accessGroup The Keychain Access Group.
/// @param completionHandler The completion handler to call when the
/// synchronized keychain read is complete. An error is passed to the
/// completion handler if the keychain read fails. Else, the object stored in
/// the keychain, or `nil` if it does not exist, is passed to the completion
/// handler.
- (void)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler;
/// Saves the given object by the given key.
/// @param object The object to store.
/// @param key The key to store the object. If there is an existing object by the key, it will be
/// overridden.
/// @param accessGroup The Keychain Access Group.
/// @param completionHandler The completion handler to call when the
/// synchronized keychain write is complete. An error is passed to the
/// completion handler if the keychain read fails. Else, the object written to
/// the keychain is passed to the completion handler.
- (void)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler;
/// Removes the object by the given key.
/// @param key The key to store the object. If there is an existing object by
/// the key, it will be overridden.
/// @param accessGroup The Keychain Access Group.
/// @param completionHandler The completion handler to call when the
/// synchronized keychain removal is complete. An error is passed to the
/// completion handler if the keychain removal fails.
- (void)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:(void (^)(NSError *_Nullable error))completionHandler;
#if TARGET_OS_OSX
/// If not `nil`, then only this keychain will be used to save and read data (see
/// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests.
@property(nonatomic, nullable) SecKeychainRef keychainRef;
#endif // TARGET_OS_OSX
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,64 @@
/*
* Copyright 2019 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
FOUNDATION_EXPORT NSString *const kGULKeychainUtilsErrorDomain;
/// A collection of helper functions that abstract away common Keychain operations.
///
/// When using this API on macOS, the corresponding target must be signed with a provisioning
/// profile that has the Keychain Sharing capability enabled.
@interface GULKeychainUtils : NSObject
/** Fetches a keychain item data matching to the provided query.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemCopyMatching` for
* details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns Data for the first Keychain Item matching the provided query or `nil` if there is not
* such an item (`outError` will be `nil` in this case) or an error occurred.
*/
+ (nullable NSData *)getItemWithQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
/** Stores data to a Keychain Item matching to the provided query. An existing Keychain Item
* matching the query parameters will be updated or a new will be created.
* @param item A Keychain Item data to store.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemAdd` and
* `SecItemUpdate` for details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns `YES` when data was successfully stored, `NO` otherwise.
*/
+ (BOOL)setItem:(NSData *)item
withQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
/** Removes a Keychain Item matching to the provided query.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemDelete` for
* details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns `YES` if the item was removed successfully or doesn't exist, `NO` otherwise.
*/
+ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,43 @@
// Copyright 2022 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>
NS_ASSUME_NONNULL_BEGIN
/// The type of network that the device is running with. Values should correspond to the NetworkType
/// values in android/play/playlog/proto/clientanalytics.proto
typedef NS_ENUM(NSInteger, GULNetworkType) {
GULNetworkTypeNone = -1,
GULNetworkTypeMobile = 0,
GULNetworkTypeWIFI = 1,
};
/// Collection of utilities to read network status information
@interface GULNetworkInfo : NSObject
/// Returns an enum indicating the network type. The enum values should be easily transferrable to
/// the NetworkType value in android/play/playlog/proto/clientanalytics.proto. Right now this always
/// returns None on platforms other than iOS. This should be updated in the future to return Wi-Fi
/// values for the other platforms when applicable.
+ (GULNetworkType)getNetworkType;
/// Returns a string indicating the radio access technology used by the app. The return value will
/// be one of CTRadioAccess constants defined in
/// https://developer.apple.com/documentation/coretelephony/cttelephonynetworkinfo/radio_access_technology_constants
+ (NSString *)getNetworkRadioType;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,196 @@
/*
* Copyright 2019 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/Environment/Public/GoogleUtilities/GULKeychainStorage.h"
#import <Security/Security.h>
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainUtils.h"
@interface GULKeychainStorage ()
@property(nonatomic, readonly) dispatch_queue_t keychainQueue;
@property(nonatomic, readonly) dispatch_queue_t inMemoryCacheQueue;
@property(nonatomic, readonly) NSString *service;
@property(nonatomic, readonly) NSCache<NSString *, id<NSSecureCoding>> *inMemoryCache;
@end
@implementation GULKeychainStorage
- (instancetype)initWithService:(NSString *)service {
NSCache *cache = [[NSCache alloc] init];
// Cache up to 5 installations.
cache.countLimit = 5;
return [self initWithService:service cache:cache];
}
- (instancetype)initWithService:(NSString *)service cache:(NSCache *)cache {
self = [super init];
if (self) {
_keychainQueue =
dispatch_queue_create("com.gul.KeychainStorage.Keychain", DISPATCH_QUEUE_SERIAL);
_inMemoryCacheQueue =
dispatch_queue_create("com.gul.KeychainStorage.InMemoryCache", DISPATCH_QUEUE_SERIAL);
_service = [service copy];
_inMemoryCache = cache;
}
return self;
}
#pragma mark - Public
- (void)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler {
dispatch_async(self.inMemoryCacheQueue, ^{
// Return cached object or fail otherwise.
id object = [self.inMemoryCache objectForKey:key];
if (object) {
completionHandler(object, nil);
} else {
// Look for the object in the keychain.
[self getObjectFromKeychainForKey:key
objectClass:objectClass
accessGroup:accessGroup
completionHandler:completionHandler];
}
});
}
- (void)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:
(void (^)(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error))completionHandler {
dispatch_async(self.inMemoryCacheQueue, ^{
// Save to the in-memory cache first.
[self.inMemoryCache setObject:object forKey:[key copy]];
dispatch_async(self.keychainQueue, ^{
// Then store the object to the keychain.
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object
requiringSecureCoding:YES
error:&error];
if (!encodedObject) {
completionHandler(nil, error);
return;
}
if (![GULKeychainUtils setItem:encodedObject withQuery:query error:&error]) {
completionHandler(nil, error);
return;
}
completionHandler(object, nil);
});
});
}
- (void)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup
completionHandler:(void (^)(NSError *_Nullable error))completionHandler {
dispatch_async(self.inMemoryCacheQueue, ^{
[self.inMemoryCache removeObjectForKey:key];
dispatch_async(self.keychainQueue, ^{
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
if (![GULKeychainUtils removeItemWithQuery:query error:&error]) {
completionHandler(error);
} else {
completionHandler(nil);
}
});
});
}
#pragma mark - Private
- (void)getObjectFromKeychainForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup
completionHandler:(void (^)(id<NSSecureCoding> _Nullable obj,
NSError *_Nullable error))completionHandler {
// Look for the object in the keychain.
dispatch_async(self.keychainQueue, ^{
NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
NSError *error;
NSData *encodedObject = [GULKeychainUtils getItemWithQuery:query error:&error];
if (error) {
completionHandler(nil, error);
return;
}
if (!encodedObject) {
completionHandler(nil, nil);
return;
}
id object = [NSKeyedUnarchiver unarchivedObjectOfClass:objectClass
fromData:encodedObject
error:&error];
if (error) {
completionHandler(nil, error);
return;
}
dispatch_async(self.inMemoryCacheQueue, ^{
// Save object to the in-memory cache if exists and return the object.
if (object) {
[self.inMemoryCache setObject:object forKey:[key copy]];
}
completionHandler(object, nil);
});
});
}
- (void)resetInMemoryCache {
[self.inMemoryCache removeAllObjects];
}
#pragma mark - Keychain
- (NSMutableDictionary<NSString *, id> *)keychainQueryWithKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup {
NSMutableDictionary<NSString *, id> *query = [NSMutableDictionary dictionary];
query[(__bridge NSString *)kSecClass] = (__bridge NSString *)kSecClassGenericPassword;
query[(__bridge NSString *)kSecAttrService] = self.service;
query[(__bridge NSString *)kSecAttrAccount] = key;
if (accessGroup) {
query[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
}
if (@available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *)) {
// Ensures that the keychain query behaves the same across all platforms.
// See go/firebase-macos-keychain-popups for details.
query[(__bridge id)kSecUseDataProtectionKeychain] = (__bridge id)kCFBooleanTrue;
}
#if TARGET_OS_OSX
if (self.keychainRef) {
query[(__bridge NSString *)kSecUseKeychain] = (__bridge id)(self.keychainRef);
query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ];
}
#endif // TARGET_OS_OSX
return query;
}
@end

View File

@ -0,0 +1,133 @@
/*
* Copyright 2019 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/Environment/Public/GoogleUtilities/GULKeychainUtils.h"
NSString *const kGULKeychainUtilsErrorDomain = @"com.gul.keychain.ErrorDomain";
@implementation GULKeychainUtils
+ (nullable NSData *)getItemWithQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError {
NSMutableDictionary *mutableGetItemQuery =
[[[self class] multiplatformQueryWithQuery:query] mutableCopy];
mutableGetItemQuery[(__bridge id)kSecReturnData] = @YES;
mutableGetItemQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
CFDataRef result = NULL;
OSStatus status =
SecItemCopyMatching((__bridge CFDictionaryRef)mutableGetItemQuery, (CFTypeRef *)&result);
if (status == errSecSuccess && result != NULL) {
if (outError) {
*outError = nil;
}
return (__bridge_transfer NSData *)result;
}
if (status == errSecItemNotFound) {
if (outError) {
*outError = nil;
}
} else {
if (outError) {
*outError = [self keychainErrorWithFunction:@"SecItemCopyMatching" status:status];
}
}
return nil;
}
+ (BOOL)setItem:(NSData *)item
withQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError {
NSDictionary *multiplatformQuery = [[self class] multiplatformQueryWithQuery:query];
NSData *existingItem = [self getItemWithQuery:multiplatformQuery error:outError];
if (outError && *outError) {
return NO;
}
OSStatus status;
if (!existingItem) {
NSMutableDictionary *mutableAddItemQuery = [multiplatformQuery mutableCopy];
mutableAddItemQuery[(__bridge id)kSecAttrAccessible] =
(__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
mutableAddItemQuery[(__bridge id)kSecValueData] = item;
status = SecItemAdd((__bridge CFDictionaryRef)mutableAddItemQuery, NULL);
} else {
NSDictionary *attributes = @{(__bridge id)kSecValueData : item};
status = SecItemUpdate((__bridge CFDictionaryRef)multiplatformQuery,
(__bridge CFDictionaryRef)attributes);
}
if (status == noErr) {
if (outError) {
*outError = nil;
}
return YES;
}
NSString *function = existingItem ? @"SecItemUpdate" : @"SecItemAdd";
if (outError) {
*outError = [self keychainErrorWithFunction:function status:status];
}
return NO;
}
+ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError {
NSDictionary *deleteItemQuery = [[self class] multiplatformQueryWithQuery:query];
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)deleteItemQuery);
if (status == noErr || status == errSecItemNotFound) {
if (outError) {
*outError = nil;
}
return YES;
}
if (outError) {
*outError = [self keychainErrorWithFunction:@"SecItemDelete" status:status];
}
return NO;
}
#pragma mark - Private
/// Returns a `NSDictionary` query that behaves the same across all platforms.
/// - Note: In practice, this API only makes a difference to keychain queries on macOS.
/// See go/firebase-macos-keychain-popups for details.
/// - Parameter query: A query to create the protected keychain query with.
+ (NSDictionary *)multiplatformQueryWithQuery:(NSDictionary *)query {
NSMutableDictionary *multiplatformQuery = [query mutableCopy];
if (@available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *)) {
multiplatformQuery[(__bridge id)kSecUseDataProtectionKeychain] = (__bridge id)kCFBooleanTrue;
}
return [multiplatformQuery copy];
}
#pragma mark - Errors
+ (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status {
NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status];
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey : failureReason};
return [NSError errorWithDomain:kGULKeychainUtilsErrorDomain code:0 userInfo:userInfo];
}
@end

View File

@ -0,0 +1,223 @@
// 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 "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
#import <os/log.h>
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLoggerLevel.h"
static dispatch_once_t sGULLoggerOnceToken;
static dispatch_queue_t sGULClientQueue;
static BOOL sGULLoggerDebugMode;
static GULLoggerLevel sGULLoggerMaximumLevel;
// Allow clients to register a version to include in the log.
static NSString *sVersion = @"";
NSString *const kGULLogSubsystem = @"com.google.utilities.logger";
static GULLoggerService kGULLoggerLogger = @"[GULLogger]";
static NSMutableDictionary<NSString *, NSMutableDictionary<GULLoggerService, os_log_t> *>
*sGULServiceLogs;
#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 GULLoggerInitialize(void) {
dispatch_once(&sGULLoggerOnceToken, ^{
sGULLoggerMaximumLevel = GULLoggerLevelNotice;
sGULClientQueue = dispatch_queue_create("GULLoggingClientQueue", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(sGULClientQueue,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
sGULServiceLogs = [NSMutableDictionary dictionary];
#ifdef DEBUG
sMessageCodeRegex = [NSRegularExpression regularExpressionWithPattern:kMessageCodePattern
options:0
error:NULL];
#endif
});
}
void GULLoggerForceDebug(void) {
// We should enable debug mode if we're not running from App Store.
if (![GULAppEnvironmentUtil isFromAppStore]) {
sGULLoggerDebugMode = YES;
GULSetLoggerLevel(GULLoggerLevelDebug);
}
}
GULLoggerLevel GULGetLoggerLevel(void) {
return sGULLoggerMaximumLevel;
}
__attribute__((no_sanitize("thread"))) void GULSetLoggerLevel(GULLoggerLevel loggerLevel) {
if (loggerLevel < GULLoggerLevelMin || loggerLevel > GULLoggerLevelMax) {
GULOSLogError(kGULLogSubsystem, kGULLoggerLogger, YES, @"I-COR000023",
@"Invalid logger level, %ld", (long)loggerLevel);
return;
}
GULLoggerInitialize();
// We should not raise the logger level if we are running from App Store.
if (loggerLevel >= GULLoggerLevelNotice && [GULAppEnvironmentUtil isFromAppStore]) {
return;
}
sGULLoggerMaximumLevel = loggerLevel;
}
/**
* Check if the level is high enough to be loggable.
*/
__attribute__((no_sanitize("thread"))) BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel) {
GULLoggerInitialize();
if (sGULLoggerDebugMode) {
return YES;
}
return (BOOL)(loggerLevel <= sGULLoggerMaximumLevel);
}
#ifdef DEBUG
void GULResetLogger(void) {
sGULLoggerOnceToken = 0;
sGULLoggerDebugMode = NO;
sGULLoggerMaximumLevel = GULLoggerLevelNotice;
}
dispatch_queue_t getGULClientQueue(void) {
return sGULClientQueue;
}
BOOL getGULLoggerDebugMode(void) {
return sGULLoggerDebugMode;
}
#endif
void GULLoggerRegisterVersion(NSString *version) {
sVersion = version;
}
os_log_type_t GULLoggerLevelToOSLogType(GULLoggerLevel level) {
switch (level) {
case GULLoggerLevelError:
return OS_LOG_TYPE_ERROR;
case GULLoggerLevelWarning:
case GULLoggerLevelNotice:
return OS_LOG_TYPE_DEFAULT;
case GULLoggerLevelInfo:
return OS_LOG_TYPE_INFO;
case GULLoggerLevelDebug:
return OS_LOG_TYPE_DEBUG;
}
}
void GULOSLogBasic(GULLoggerLevel level,
NSString *subsystem,
NSString *category,
BOOL forceLog,
NSString *messageCode,
NSString *message,
va_list args_ptr) {
GULLoggerInitialize();
if (!(level <= sGULLoggerMaximumLevel || sGULLoggerDebugMode || forceLog)) {
return;
}
#ifdef DEBUG
NSCAssert(messageCode.length == 11, @"Incorrect message code length.");
NSRange messageCodeRange = NSMakeRange(0, messageCode.length);
NSUInteger __unused numberOfMatches =
[sMessageCodeRegex numberOfMatchesInString:messageCode options:0 range:messageCodeRange];
NSCAssert(numberOfMatches == 1, @"Incorrect message code format.");
#endif
NSString *logMsg;
if (args_ptr == NULL) {
logMsg = message;
} else {
logMsg = [[NSString alloc] initWithFormat:message arguments:args_ptr];
}
logMsg = [NSString stringWithFormat:@"%@ - %@[%@] %@", sVersion, category, messageCode, logMsg];
dispatch_async(sGULClientQueue, ^{
NSMutableDictionary<GULLoggerService, os_log_t> *subsystemLogs = sGULServiceLogs[subsystem];
if (!subsystemLogs) {
subsystemLogs = [NSMutableDictionary dictionary];
sGULServiceLogs[subsystem] = subsystemLogs;
}
os_log_t serviceLog = [subsystemLogs objectForKey:subsystem];
if (!serviceLog) {
serviceLog = os_log_create(subsystem.UTF8String, category.UTF8String);
subsystemLogs[category] = serviceLog;
}
os_log_with_type(serviceLog, GULLoggerLevelToOSLogType(level), "%{public}@", logMsg);
});
}
/**
* Generates the logging functions using macros.
*
* Calling GULLogError({service}, @"I-XYZ000001", @"Configure %@ failed.", @"blah") shows:
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Error> [{service}][I-XYZ000001] Configure blah failed.
* Calling GULLogDebug({service}, @"I-XYZ000001", @"Configure succeed.") shows:
* yyyy-mm-dd hh:mm:ss.SSS sender[PID] <Debug> [{service}][I-XYZ000001] Configure succeed.
*/
#define GUL_LOGGING_FUNCTION(level) \
void GULOSLog##level(NSString *subsystem, NSString *category, BOOL force, NSString *messageCode, \
NSString *message, ...) { \
va_list args_ptr; \
va_start(args_ptr, message); \
GULOSLogBasic(GULLoggerLevel##level, subsystem, category, 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_LOGGING_FUNCTION
#pragma mark - GULLoggerWrapper
@implementation GULLoggerWrapper
+ (void)logWithLevel:(GULLoggerLevel)level
subsystem:(NSString *)subsystem
category:(GULLoggerService)category
messageCode:(NSString *)messageCode
message:(NSString *)message
arguments:(va_list)args {
GULOSLogBasic(level, subsystem, category, NO, messageCode, message, args);
}
+ (void)logWithLevel:(GULLoggerLevel)level
withService:(GULLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args {
GULOSLogBasic(level, kGULLogSubsystem, service, NO, messageCode, message, args);
}
@end

View File

@ -0,0 +1,165 @@
/*
* 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 "GULLoggerLevel.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The services used in the logger.
*
* DEPRECATED; use NSString instead.
*/
typedef NSString *const GULLoggerService;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/// Used for other GoogleUtilities logging.
extern NSString *const kGULLogSubsystem;
/// Initialize GULLogger.
extern void GULLoggerInitialize(void);
/// Override log level to Debug.
void GULLoggerForceDebug(void);
/// Gets the current `GULLoggerLevel`.
extern GULLoggerLevel GULGetLoggerLevel(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(NSString *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 GULOSLogBasic(GULLoggerLevel level,
NSString *subsystem,
NSString *category,
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 GULOSLogError(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogWarning(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogNotice(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogInfo(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
extern void GULOSLogDebug(NSString *subsystem,
GULLoggerService category,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(5, 6);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@interface GULLoggerWrapper : NSObject
/// Objective-C wrapper for `GULOSLogBasic` to allow weak linking to `GULLogger`.
///
/// - Parameters:
/// - level: The log level (one of the `GULLoggerLevel` enum values).
/// - subsystem: An identifier for the subsystem performing logging, e.g., `com.example.logger`.
/// - category: The category name within the `subsystem` to group related messages, e.g.,
/// `[GoogleUtilities/Example]`.
/// - messageCode: The 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".
/// - message: The message to log, which may be a format string.
/// - arguments: The variable arguments list obtained from calling va_start, used when message is
/// a format string; optional if `message` is not a format string.
+ (void)logWithLevel:(GULLoggerLevel)level
subsystem:(NSString *)subsystem
category:(NSString *)category
messageCode:(NSString *)messageCode
message:(NSString *)message
arguments:(va_list)args;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,47 @@
/*
* 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
/// The log levels used by internal logging.
typedef NS_ENUM(NSInteger, GULLoggerLevel) {
/// Error level, corresponding to `OS_LOG_TYPE_ERROR`.
GULLoggerLevelError = 3, // For backwards compatibility, the enum value matches `ASL_LEVEL_ERR`.
/// Warning level, corresponding to `OS_LOG_TYPE_DEFAULT`.
///
/// > Note: Since OSLog doesn't have a WARNING type, this is equivalent to `GULLoggerLevelNotice`.
GULLoggerLevelWarning = 4, // For backwards compatibility, the value matches `ASL_LEVEL_WARNING`.
/// Notice level, corresponding to `OS_LOG_TYPE_DEFAULT`.
GULLoggerLevelNotice = 5, // For backwards compatibility, the value matches `ASL_LEVEL_NOTICE`.
/// Info level, corresponding to `OS_LOG_TYPE_INFO`.
GULLoggerLevelInfo = 6, // For backwards compatibility, the enum value matches `ASL_LEVEL_INFO`.
/// Debug level, corresponding to `OS_LOG_TYPE_DEBUG`.
GULLoggerLevelDebug = 7, // For backwards compatibility, the value matches `ASL_LEVEL_DEBUG`.
/// The minimum (most severe) supported logging level.
GULLoggerLevelMin = GULLoggerLevelError,
/// The maximum (least severe) supported logging level.
GULLoggerLevelMax = GULLoggerLevelDebug
} NS_SWIFT_NAME(GoogleLoggerLevel);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,154 @@
// 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 "GoogleUtilities/MethodSwizzler/Public/GoogleUtilities/GULSwizzler.h"
#import <objc/runtime.h>
#ifdef DEBUG
#import "GoogleUtilities/Common/GULLoggerCodes.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
static GULLoggerService kGULLoggerSwizzler = @"[GoogleUtilities/MethodSwizzler]";
#endif
dispatch_queue_t GetGULSwizzlingQueue(void) {
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.google.GULSwizzler", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
@implementation GULSwizzler
+ (void)swizzleClass:(Class)aClass
selector:(SEL)selector
isClassSelector:(BOOL)isClassSelector
withBlock:(nullable id)block {
dispatch_sync(GetGULSwizzlingQueue(), ^{
NSAssert(selector, @"The selector cannot be NULL");
NSAssert(aClass, @"The class cannot be Nil");
Class resolvedClass = aClass;
Method method = nil;
if (isClassSelector) {
method = class_getClassMethod(aClass, selector);
resolvedClass = object_getClass(aClass);
} else {
method = class_getInstanceMethod(aClass, selector);
}
NSAssert(method, @"You're attempting to swizzle a method that doesn't exist. (%@, %@)",
NSStringFromClass(resolvedClass), NSStringFromSelector(selector));
IMP newImp = imp_implementationWithBlock(block);
#ifdef DEBUG
IMP currentImp = class_getMethodImplementation(resolvedClass, selector);
Class class = NSClassFromString(@"GULSwizzlingCache");
if (class) {
SEL cacheSelector = NSSelectorFromString(@"cacheCurrentIMP:forNewIMP:forClass:withSelector:");
NSMethodSignature *methodSignature = [class methodSignatureForSelector:cacheSelector];
if (methodSignature != nil) {
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature];
[inv setSelector:cacheSelector];
[inv setTarget:class];
[inv setArgument:&(currentImp) atIndex:2];
[inv setArgument:&(newImp) atIndex:3];
[inv setArgument:&(resolvedClass) atIndex:4];
[inv setArgument:(void *_Nonnull)&(selector) atIndex:5];
[inv invoke];
}
}
#endif
const char *typeEncoding = method_getTypeEncoding(method);
__unused IMP originalImpOfClass =
class_replaceMethod(resolvedClass, selector, newImp, typeEncoding);
#ifdef DEBUG
// If !originalImpOfClass, then the IMP came from a superclass.
if (originalImpOfClass) {
SEL selector = NSSelectorFromString(@"originalIMPOfCurrentIMP:");
NSMethodSignature *methodSignature = [class methodSignatureForSelector:selector];
if (methodSignature != nil) {
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:methodSignature];
[inv setSelector:selector];
[inv setTarget:class];
[inv setArgument:&(currentImp) atIndex:2];
[inv invoke];
IMP testOriginal;
[inv getReturnValue:&testOriginal];
if (originalImpOfClass != testOriginal) {
GULOSLogWarning(
kGULLogSubsystem, kGULLoggerSwizzler, NO,
[NSString
stringWithFormat:@"I-SWZ%06ld", (long)kGULSwizzlerMessageCodeMethodSwizzling000],
@"Swizzling class: %@ SEL:%@ after it has been previously been swizzled.",
NSStringFromClass(resolvedClass), NSStringFromSelector(selector));
}
}
}
#endif
});
}
+ (nullable IMP)currentImplementationForClass:(Class)aClass
selector:(SEL)selector
isClassSelector:(BOOL)isClassSelector {
NSAssert(selector, @"The selector cannot be NULL");
NSAssert(aClass, @"The class cannot be Nil");
if (selector == NULL || aClass == nil) {
return nil;
}
__block IMP currentIMP = nil;
dispatch_sync(GetGULSwizzlingQueue(), ^{
Method method = nil;
if (isClassSelector) {
method = class_getClassMethod(aClass, selector);
} else {
method = class_getInstanceMethod(aClass, selector);
}
NSAssert(method, @"The Method for this class/selector combo doesn't exist (%@, %@).",
NSStringFromClass(aClass), NSStringFromSelector(selector));
if (method == nil) {
return;
}
currentIMP = method_getImplementation(method);
NSAssert(currentIMP, @"The IMP for this class/selector combo doesn't exist (%@, %@).",
NSStringFromClass(aClass), NSStringFromSelector(selector));
});
return currentIMP;
}
+ (BOOL)selector:(SEL)selector existsInClass:(Class)aClass isClassSelector:(BOOL)isClassSelector {
Method method = isClassSelector ? class_getClassMethod(aClass, selector)
: class_getInstanceMethod(aClass, selector);
return method != nil;
}
+ (NSArray<id> *)ivarObjectsForObject:(id)object {
NSMutableArray *array = [NSMutableArray array];
unsigned int count;
Ivar *vars = class_copyIvarList([object class], &count);
for (NSUInteger i = 0; i < count; i++) {
const char *typeEncoding = ivar_getTypeEncoding(vars[i]);
// Check to see if the ivar is an object.
if (strncmp(typeEncoding, "@", 1) == 0) {
id ivarObject = object_getIvar(object, vars[i]);
[array addObject:ivarObject];
}
}
free(vars);
return array;
}
@end

View File

@ -0,0 +1,213 @@
/*
* 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>
NS_ASSUME_NONNULL_BEGIN
/**
* GULOriginalIMPConvenienceMacros.h
*
* This header contains convenience macros for invoking the original IMP of a swizzled method.
*/
/**
* Invokes original IMP when the original selector takes no arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
*/
#define GUL_INVOKE_ORIGINAL_IMP0(__receivingObject, __swizzledSEL, __returnType, __originalIMP) \
((__returnType(*)(id, SEL))__originalIMP)(__receivingObject, __swizzledSEL)
/**
* Invokes original IMP when the original selector takes 1 argument.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP1(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1) \
((__returnType(*)(id, SEL, __typeof__(__arg1)))__originalIMP)(__receivingObject, __swizzledSEL, \
__arg1)
/**
* Invokes original IMP when the original selector takes 2 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP2(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2)))__originalIMP)( \
__receivingObject, __swizzledSEL, __arg1, __arg2)
/**
* Invokes original IMP when the original selector takes 3 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
* @param __arg3 The third argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP3(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2, __arg3) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), \
__typeof__(__arg3)))__originalIMP)(__receivingObject, __swizzledSEL, __arg1, \
__arg2, __arg3)
/**
* Invokes original IMP when the original selector takes 4 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
* @param __arg3 The third argument.
* @param __arg4 The fourth argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP4(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2, __arg3, __arg4) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \
__typeof__(__arg4)))__originalIMP)(__receivingObject, __swizzledSEL, __arg1, \
__arg2, __arg3, __arg4)
/**
* Invokes original IMP when the original selector takes 5 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
* @param __arg3 The third argument.
* @param __arg4 The fourth argument.
* @param __arg5 The fifth argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP5(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2, __arg3, __arg4, __arg5) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \
__typeof__(__arg4), __typeof__(__arg5)))__originalIMP)( \
__receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5)
/**
* Invokes original IMP when the original selector takes 6 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
* @param __arg3 The third argument.
* @param __arg4 The fourth argument.
* @param __arg5 The fifth argument.
* @param __arg6 The sixth argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP6(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2, __arg3, __arg4, __arg5, __arg6) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \
__typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6)))__originalIMP)( \
__receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6)
/**
* Invokes original IMP when the original selector takes 7 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
* @param __arg3 The third argument.
* @param __arg4 The fourth argument.
* @param __arg5 The fifth argument.
* @param __arg6 The sixth argument.
* @param __arg7 The seventh argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP7(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \
__typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \
__typeof__(__arg7)))__originalIMP)( \
__receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7)
/**
* Invokes original IMP when the original selector takes 8 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
* @param __arg3 The third argument.
* @param __arg4 The fourth argument.
* @param __arg5 The fifth argument.
* @param __arg6 The sixth argument.
* @param __arg7 The seventh argument.
* @param __arg8 The eighth argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP8(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \
__typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \
__typeof__(__arg7), __typeof__(__arg8)))__originalIMP)( \
__receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, \
__arg8)
/**
* Invokes original IMP when the original selector takes 9 arguments.
*
* @param __receivingObject The object on which the IMP is invoked.
* @param __swizzledSEL The selector used for swizzling.
* @param __returnType The return type of the original implementation.
* @param __originalIMP The original IMP.
* @param __arg1 The first argument.
* @param __arg2 The second argument.
* @param __arg3 The third argument.
* @param __arg4 The fourth argument.
* @param __arg5 The fifth argument.
* @param __arg6 The sixth argument.
* @param __arg7 The seventh argument.
* @param __arg8 The eighth argument.
* @param __arg9 The ninth argument.
*/
#define GUL_INVOKE_ORIGINAL_IMP9(__receivingObject, __swizzledSEL, __returnType, __originalIMP, \
__arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, __arg8, \
__arg9) \
((__returnType(*)(id, SEL, __typeof__(__arg1), __typeof__(__arg2), __typeof__(__arg3), \
__typeof__(__arg4), __typeof__(__arg5), __typeof__(__arg6), \
__typeof__(__arg7), __typeof__(__arg8), __typeof__(__arg9)))__originalIMP)( \
__receivingObject, __swizzledSEL, __arg1, __arg2, __arg3, __arg4, __arg5, __arg6, __arg7, \
__arg8, __arg9)
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,71 @@
/*
* 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>
NS_ASSUME_NONNULL_BEGIN
/** This class handles the runtime manipulation necessary to instrument selectors. It stores the
* classes and selectors that have been swizzled, and runs all operations on its own queue.
*/
@interface GULSwizzler : NSObject
/** Manipulates the Objective-C runtime to replace the original IMP with the supplied block.
*
* @param aClass The class to swizzle.
* @param selector The selector of the class to swizzle.
* @param isClassSelector A BOOL specifying whether the selector is a class or instance selector.
* @param block The block that replaces the original IMP.
*/
+ (void)swizzleClass:(Class)aClass
selector:(SEL)selector
isClassSelector:(BOOL)isClassSelector
withBlock:(nullable id)block;
/** Returns the current IMP for the given class and selector.
*
* @param aClass The class to use.
* @param selector The selector to find the implementation of.
* @param isClassSelector A BOOL specifying whether the selector is a class or instance selector.
* @return The implementation of the selector in the runtime.
*/
+ (nullable IMP)currentImplementationForClass:(Class)aClass
selector:(SEL)selector
isClassSelector:(BOOL)isClassSelector;
/** Checks the runtime to see if a selector exists on a class. If a property is declared as
* @dynamic, we have a reverse swizzling situation, where the implementation of a method exists
* only in concrete subclasses, and NOT in the superclass. We can detect that situation using
* this helper method. Similarly, we can detect situations where a class doesn't implement a
* protocol method.
*
* @param selector The selector to check for.
* @param aClass The class to check.
* @param isClassSelector A BOOL specifying whether the selector is a class or instance selector.
* @return YES if the method was found in this selector/class combination, NO otherwise.
*/
+ (BOOL)selector:(SEL)selector existsInClass:(Class)aClass isClassSelector:(BOOL)isClassSelector;
/** Returns a list of all Objective-C (and not primitive) ivars contained by the given object.
*
* @param object The object whose ivars will be iterated.
* @return The list of ivar objects.
*/
+ (NSArray<id> *)ivarObjectsForObject:(id)object;
@end
NS_ASSUME_NONNULL_END

View File

@ -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 "GoogleUtilities/NSData+zlib/Public/GoogleUtilities/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)
+ (nullable 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;
}
+ (nullable 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

View File

@ -0,0 +1,53 @@
// 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
/// 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.
+ (nullable 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.
+ (nullable 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
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,101 @@
// 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/Network/Public/GoogleUtilities/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 objectForKey:key];
});
return object;
}
- (void)setObject:(id)object forKey:(id<NSCopying>)key {
dispatch_async(_queue, ^{
[self->_objects setObject:object forKey:key];
});
}
- (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 {
__block id object;
dispatch_sync(_queue, ^{
object = self->_objects[key];
});
return object;
}
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
dispatch_async(_queue, ^{
self->_objects[key] = obj;
});
}
- (NSDictionary *)dictionary {
__block NSDictionary *dictionary;
dispatch_sync(_queue, ^{
dictionary = [self->_objects copy];
});
return dictionary;
}
@end

View File

@ -0,0 +1,406 @@
// 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/Network/Public/GoogleUtilities/GULNetwork.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
#import "GoogleUtilities/NSData+zlib/Public/GoogleUtilities/GULNSData+zlib.h"
#import "GoogleUtilities/Network/GULNetworkInternal.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h"
#import "GoogleUtilities/Reachability/Public/GoogleUtilities/GULReachabilityChecker.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];
}
- (nullable NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler {
return [self postURL:url
headers:nil
payload:payload
queue:queue
usingBackgroundSession:usingBackgroundSession
completionHandler:handler];
}
- (nullable NSString *)postURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
payload:(NSData *)payload
queue:(nullable 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.allHTTPHeaderFields = headers;
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;
}
- (nullable NSString *)getURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
queue:(nullable 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:)]) {
GULOSLogError(
kGULLogSubsystem, 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);
GULOSLogBasic((GULLoggerLevel)logLevel, kGULLogSubsystem, 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

View File

@ -0,0 +1,41 @@
// 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/Network/Public/GoogleUtilities/GULNetworkConstants.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.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]";

View File

@ -0,0 +1,24 @@
/*
* 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/Logger/Public/GoogleUtilities/GULLogger.h"
extern NSString *const kGULNetworkErrorDomain;
/// The logger service for GULNetwork.
extern GULLoggerService kGULLoggerNetwork;

View File

@ -0,0 +1,729 @@
// 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/Network/Public/GoogleUtilities/GULNetworkURLSession.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
#import "GoogleUtilities/Network/GULNetworkInternal.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkConstants.h"
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULNetworkMessageCode.h"
@interface GULNetworkURLSession () <NSURLSessionDelegate,
NSURLSessionDataDelegate,
NSURLSessionDownloadDelegate,
NSURLSessionTaskDelegate>
@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;
/// The session configuration.
NSURLSessionConfiguration *_sessionConfig;
/// The current NSURLSession.
NSURLSession *__weak _Nullable _URLSession;
/// 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.
#if TARGET_OS_TV
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
#else
NSArray *paths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
#endif
NSString *storageDirectory = paths.firstObject;
NSArray *tempPathComponents = @[
storageDirectory, 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 {
GULOSLogError(kGULLogSubsystem, 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`, and returns an ID of the connection.
- (nullable NSString *)sessionIDFromAsyncPOSTRequest:(NSURLRequest *)request
completionHandler:
(GULNetworkURLSessionCompletionHandler)handler {
// NSURLSessionUploadTask does not work with NSData in the background.
// To avoid this issue, write the data to a temporary file to upload it.
// Make a temporary file with the data subset.
_uploadingFileURL = [self temporaryFilePathWithSessionID:_sessionID];
NSError *writeError;
NSURLSessionUploadTask *postRequestTask;
NSURLSession *session;
BOOL didWriteFile = NO;
// Clean up the entire temp folder to avoid temp files that remain in case the previous session
// crashed and did not clean up.
[self maybeRemoveTempFilesAtURL:_networkDirectoryURL
expiringTime:kGULNetworkTempFolderExpireTime];
// If there is no background network enabled, no need to write to file. This will allow default
// network session which runs on the foreground.
if (_backgroundNetworkEnabled && [self ensureTemporaryDirectoryExists]) {
didWriteFile = [request.HTTPBody writeToFile:_uploadingFileURL.path
options:NSDataWritingAtomic
error:&writeError];
if (writeError) {
[_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
messageCode:kGULNetworkMessageCodeURLSession000
message:@"Failed to write request data to file"
context:writeError];
}
}
if (didWriteFile) {
// Exclude this file from backing up to iTunes. There are conflicting reports that excluding
// directory from backing up does not exclude files of that directory from backing up.
[self excludeFromBackupForURL:_uploadingFileURL];
_sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
} 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]];
// To avoid a runtime warning in Xcode 15 Beta 4, the given `URLRequest`
// should have a nil `HTTPBody`. To workaround this, the given `URLRequest`
// is copied and the `HTTPBody` data is removed.
NSData *givenRequestHTTPBody = [request.HTTPBody copy];
NSMutableURLRequest *requestWithoutHTTPBody = [request mutableCopy];
requestWithoutHTTPBody.HTTPBody = nil;
if (didWriteFile) {
postRequestTask = [session uploadTaskWithRequest:requestWithoutHTTPBody
fromFile:_uploadingFileURL];
} else {
postRequestTask = [session uploadTaskWithRequest:requestWithoutHTTPBody
fromData:givenRequestHTTPBody];
}
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`, and returns an ID of the session.
- (nullable NSString *)sessionIDFromAsyncGETRequest:(NSURLRequest *)request
completionHandler:(GULNetworkURLSessionCompletionHandler)handler {
if (_backgroundNetworkEnabled) {
_sessionConfig = [self backgroundSessionConfigWithSessionID:_sessionID];
} else {
_sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
}
[self populateSessionConfig:_sessionConfig withRequest:request];
// Do not cache the GET request.
_sessionConfig.URLCache = nil;
NSURLSession *session = [NSURLSession sessionWithConfiguration:_sessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request];
if (!session || !downloadTask) {
NSError *error = [[NSError alloc]
initWithDomain:kGULNetworkErrorDomain
code:GULErrorCodeNetworkRequestCreation
userInfo:@{kGULNetworkErrorContext : @"Cannot create network session"}];
[self callCompletionHandler:handler withResponse:nil data:nil error:error];
return nil;
}
_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 - NSURLSessionDataDelegate
/// Called by the NSURLSession when the data task has received some of the expected data.
/// 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
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
@synchronized(self) {
NSMutableData *mutableData = [[NSMutableData alloc] init];
if (_downloadedData) {
mutableData = _downloadedData.mutableCopy;
}
[mutableData appendData:data];
_downloadedData = mutableData;
}
}
#pragma mark - NSURLSessionTaskDelegate
/// Called by the NSURLSession once the download task is completed. The file is saved in the
/// provided URL so we need to read the data and store into _downloadedData. Once the session is
/// completed, URLSession:task:didCompleteWithError will be called and the completion handler will
/// be called with the downloaded data.
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)task
didFinishDownloadingToURL:(NSURL *)url {
if (!url.path) {
[_loggerDelegate
GULNetwork_logWithLevel:kGULNetworkLogLevelError
messageCode:kGULNetworkMessageCodeURLSession001
message:@"Unable to read downloaded data from empty temp path"];
_downloadedData = nil;
return;
}
NSError *error;
_downloadedData = [NSData dataWithContentsOfFile:url.path options:0 error:&error];
if (error) {
[_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelError
messageCode:kGULNetworkMessageCodeURLSession002
message:@"Cannot read the content of downloaded data"
context:error];
_downloadedData = nil;
}
}
#if TARGET_OS_IOS || TARGET_OS_TV
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
[_loggerDelegate GULNetwork_logWithLevel:kGULNetworkLogLevelDebug
messageCode:kGULNetworkMessageCodeURLSession003
message:@"Background session finished"
context:session.configuration.identifier];
[self callSystemCompletionHandler:session.configuration.identifier];
}
#endif
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
// Avoid any chance of recursive behavior leading to it being used repeatedly.
GULNetworkURLSessionCompletionHandler handler = _completionHandler;
_completionHandler = nil;
if (task.response) {
// The following assertion should always be true for HTTP requests, see https://goo.gl/gVLxT7.
NSAssert([task.response isKindOfClass:[NSHTTPURLResponse class]], @"URL response must be HTTP");
// The server responded so ignore the error created by the system.
error = nil;
} else if (!error) {
error = [[NSError alloc]
initWithDomain:kGULNetworkErrorDomain
code:GULErrorCodeNetworkInvalidResponse
userInfo:@{kGULNetworkErrorContext : @"Network Error: Empty network response"}];
}
[self callCompletionHandler:handler
withResponse:(NSHTTPURLResponse *)task.response
data:_downloadedData
error:error];
// Remove the temp file to avoid trashing devices with lots of temp files.
[self removeTempItemAtURL:_uploadingFileURL];
// Try to clean up stale files again.
[self maybeRemoveTempFilesAtURL:_networkDirectoryURL
expiringTime:kGULNetworkTempFolderExpireTime];
// 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 {
// 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, ^{
BOOL shouldAllow;
CFErrorRef errorRef = NULL;
@synchronized([GULNetworkURLSession class]) {
shouldAllow = SecTrustEvaluateWithError(serverTrust, &errorRef);
}
if (errorRef) {
[self->_loggerDelegate
GULNetwork_logWithLevel:kGULNetworkLogLevelError
messageCode:kGULNetworkMessageCodeURLSession008
message:@"Cannot evaluate server trust. Error, host"
contexts:@[ @((int)CFErrorGetCode(errorRef)), self->_request.URL ]];
CFRelease(errorRef);
}
// 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 {
return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:sessionID];
}
- (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 {
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 {
sessionConfig.HTTPAdditionalHeaders = request.allHTTPHeaderFields;
sessionConfig.timeoutIntervalForRequest = request.timeoutInterval;
sessionConfig.timeoutIntervalForResource = request.timeoutInterval;
sessionConfig.requestCachePolicy = request.cachePolicy;
}
@end

View File

@ -0,0 +1,50 @@
/*
* 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>
NS_ASSUME_NONNULL_BEGIN
/// 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
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,101 @@
/*
* 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"
NS_ASSUME_NONNULL_BEGIN
/// 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.
- (nullable NSString *)postURL:(NSURL *)url
payload:(NSData *)payload
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
/// Compresses and sends a POST request with the provided headers and 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.
- (nullable NSString *)postURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
payload:(NSData *)payload
queue:(nullable 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.
- (nullable NSString *)getURL:(NSURL *)url
headers:(nullable NSDictionary *)headers
queue:(nullable dispatch_queue_t)queue
usingBackgroundSession:(BOOL)usingBackgroundSession
completionHandler:(GULNetworkCompletionHandler)handler;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,75 @@
/*
* 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>
NS_ASSUME_NONNULL_BEGIN
/// 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;
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,53 @@
/*
* 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 "GULNetworkMessageCode.h"
NS_ASSUME_NONNULL_BEGIN
/// The log levels used by GULNetworkLogger.
typedef NS_ENUM(NSInteger, GULNetworkLogLevel) {
kGULNetworkLogLevelError = 3,
kGULNetworkLogLevelWarning = 4,
kGULNetworkLogLevelInfo = 6,
kGULNetworkLogLevelDebug = 7,
};
@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
NS_ASSUME_NONNULL_END

View File

@ -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>
NS_ASSUME_NONNULL_BEGIN
// 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
};
NS_ASSUME_NONNULL_END

View File

@ -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

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C56D.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@ -0,0 +1,48 @@
/*
* 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/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h"
#if !TARGET_OS_WATCH
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;
};
#endif
@interface GULReachabilityChecker (Internal)
- (const struct GULReachabilityApi *)reachabilityApi;
- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi;
@end

View File

@ -0,0 +1,263 @@
// 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/Reachability/Public/GoogleUtilities/GULReachabilityChecker.h"
#import "GoogleUtilities/Reachability/GULReachabilityChecker+Internal.h"
#import "GoogleUtilities/Reachability/GULReachabilityMessageCode.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
static GULLoggerService kGULLoggerReachability = @"[GULReachability]";
#if !TARGET_OS_WATCH
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";
#endif
@interface GULReachabilityChecker ()
@property(nonatomic, assign) const struct GULReachabilityApi *reachabilityApi;
@property(nonatomic, assign) GULReachabilityStatus reachabilityStatus;
@property(nonatomic, copy) NSString *host;
#if !TARGET_OS_WATCH
@property(nonatomic, assign) SCNetworkReachabilityRef reachability;
#endif
@end
@implementation GULReachabilityChecker
@synthesize reachabilityApi = reachabilityApi_;
#if !TARGET_OS_WATCH
@synthesize reachability = reachability_;
#endif
- (const struct GULReachabilityApi *)reachabilityApi {
return reachabilityApi_;
}
- (void)setReachabilityApi:(const struct GULReachabilityApi *)reachabilityApi {
#if !TARGET_OS_WATCH
if (reachability_) {
GULOSLogError(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode000],
@"Cannot change reachability API while reachability is running. "
@"Call stop first.");
return;
}
reachabilityApi_ = reachabilityApi;
#endif
}
@synthesize reachabilityStatus = reachabilityStatus_;
@synthesize host = host_;
@synthesize reachabilityDelegate = reachabilityDelegate_;
- (BOOL)isActive {
#if !TARGET_OS_WATCH
return reachability_ != nil;
#else
return NO;
#endif
}
- (void)setReachabilityDelegate:(id<GULReachabilityDelegate>)reachabilityDelegate {
if (reachabilityDelegate &&
(![(NSObject *)reachabilityDelegate conformsToProtocol:@protocol(GULReachabilityDelegate)])) {
GULOSLogError(kGULLogSubsystem, 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) {
GULOSLogError(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode001],
@"Invalid host specified");
return nil;
}
if (self) {
#if !TARGET_OS_WATCH
[self setReachabilityDelegate:reachabilityDelegate];
reachabilityApi_ = &kGULDefaultReachabilityApi;
reachabilityStatus_ = kGULReachabilityUnknown;
host_ = [host copy];
reachability_ = nil;
#endif
}
return self;
}
- (void)dealloc {
reachabilityDelegate_ = nil;
[self stop];
}
- (BOOL)start {
#if TARGET_OS_WATCH
return NO;
#else
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;
GULOSLogError(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode002],
@"Failed to start reachability handle");
return NO;
}
}
GULOSLogDebug(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode003],
@"Monitoring the network status");
return YES;
#endif
}
- (void)stop {
#if !TARGET_OS_WATCH
if (reachability_) {
reachabilityStatus_ = kGULReachabilityUnknown;
reachabilityApi_->unscheduleFromRunLoopFn(reachability_, CFRunLoopGetMain(),
kCFRunLoopCommonModes);
reachabilityApi_->releaseFn(reachability_);
reachability_ = nil;
}
#endif
}
#if !TARGET_OS_WATCH
- (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 || TARGET_OS_VISION
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 || TARGET_OS_VISION
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;
}
GULOSLogDebug(kGULLogSubsystem, kGULLoggerReachability, NO,
[NSString stringWithFormat:@"I-REA%06ld", (long)kGULReachabilityMessageCode004],
@"Network status has changed. Code:%@, status:%@", @(status),
reachabilityStatusString);
reachabilityStatus_ = status;
[reachabilityDelegate_ reachability:self statusChanged:reachabilityStatus_];
}
}
#endif
@end
#if !TARGET_OS_WATCH
static void ReachabilityCallback(SCNetworkReachabilityRef reachability,
SCNetworkReachabilityFlags flags,
void *info) {
GULReachabilityChecker *checker = (__bridge GULReachabilityChecker *)info;
[checker reachabilityFlagsChanged:flags];
}
#endif
// 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];
}
}

View File

@ -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
};

View File

@ -0,0 +1,83 @@
/*
* 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>
#if !TARGET_OS_WATCH
#import <SystemConfiguration/SystemConfiguration.h>
#endif
NS_ASSUME_NONNULL_BEGIN
/// 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
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,165 @@
// 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 "GoogleUtilities/UserDefaults/Public/GoogleUtilities/GULUserDefaults.h"
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
NS_ASSUME_NONNULL_BEGIN
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 ()
@property(nonatomic, readonly) NSUserDefaults *userDefaults;
@end
@implementation GULUserDefaults
+ (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) {
_userDefaults = name.length ? [[NSUserDefaults alloc] initWithSuiteName:name]
: [NSUserDefaults standardUserDefaults];
}
return self;
}
- (nullable id)objectForKey:(NSString *)defaultName {
NSString *key = [defaultName copy];
if (![key isKindOfClass:[NSString class]] || !key.length) {
GULOSLogWarning(kGULLogSubsystem, @"<GoogleUtilities>", NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeyGet],
@"Cannot get object for invalid user default key.");
return nil;
}
return [self.userDefaults objectForKey:key];
}
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName {
NSString *key = [defaultName copy];
if (![key isKindOfClass:[NSString class]] || !key.length) {
GULOSLogWarning(kGULLogSubsystem, kGULLogUserDefaultsService, NO,
[NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeySet],
@"Cannot set object for invalid user default key.");
return;
}
if (!value) {
[self.userDefaults removeObjectForKey:key];
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) {
GULOSLogWarning(
kGULLogSubsystem, 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;
}
[self.userDefaults setObject:value forKey:key];
}
- (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];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,105 @@
// 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;
@end
NS_ASSUME_NONNULL_END

224
Pods/GoogleUtilities/LICENSE generated Normal file
View File

@ -0,0 +1,224 @@
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.
================================================================================
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.

189
Pods/GoogleUtilities/README.md generated Normal file
View File

@ -0,0 +1,189 @@
[![Version](https://img.shields.io/cocoapods/v/GoogleUtilities.svg?style=flat)](https://cocoapods.org/pods/GoogleUtilities)
[![License](https://img.shields.io/cocoapods/l/GoogleUtilities.svg?style=flat)](https://cocoapods.org/pods/GoogleUtilities)
[![Platform](https://img.shields.io/cocoapods/p/GoogleUtilities.svg?style=flat)](https://cocoapods.org/pods/GoogleUtilities)
[![Actions Status][gh-google-utilities-badge]][gh-actions]
# GoogleUtilities
GoogleUtilities provides a set of utilities for Firebase and other Google SDKs for Apple platform
development.
The utilities are not directly supported for non-Google library usage.
## Integration Testing
These instructions apply to minor and patch version updates. Major versions need
a customized adaptation.
After the CI is green:
* Determine the next version for release by checking the
[tagged releases](https://github.com/google/GoogleUtilities/tags).
Ensure that the next release version keeps the Swift PM and CocoaPods versions in sync.
* Verify that the releasing version is the latest entry in the [CHANGELOG.md](CHANGELOG.md),
updating it if necessary.
* Update the version in the podspec to match the latest entry in the [CHANGELOG.md](CHANGELOG.md)
* Checkout the `main` branch and ensure it is up to date
```console
git checkout main
git pull
```
* Add the CocoaPods tag (`{version}` will be the latest version in the [podspec](GoogleUtilities.podspec#L3))
```console
git tag CocoaPods-{version}
git push origin CocoaPods-{version}
```
* Push the podspec to the designated repo
* If this version of GoogleUtilities is intended to launch **before or with** the next Firebase release:
<details>
<summary>Push to <b>SpecsStaging</b></summary>
```console
pod repo push --skip-tests --use-json staging GoogleUtilities.podspec
```
If the command fails with `Unable to find the 'staging' repo.`, add the staging repo with:
```console
pod repo add staging git@github.com:firebase/SpecsStaging.git
```
</details>
* Otherwise:
<details>
<summary>Push to <b>SpecsDev</b></summary>
```console
pod repo push --skip-tests --use-json dev GoogleUtilities.podspec
```
If the command fails with `Unable to find the 'dev' repo.`, add the dev repo with:
```console
pod repo add dev git@github.com:firebase/SpecsDev.git
```
</details>
* Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile`.
* On google3, run copybara using the command below. Then, start a global TAP on the generated CL. Deflake as needed.
```console
third_party/firebase/ios/Releases/run_copy_bara.py --directory GoogleUtilities --branch main
```
## Publishing
The release process is as follows:
1. [Tag and release for Swift PM](#swift-package-manager)
2. [Publish to CocoaPods](#cocoapods)
3. [Create GitHub Release](#create-github-release)
4. [Perform post release cleanup](#post-release-cleanup)
### Swift Package Manager
By creating and [pushing a tag](https://github.com/google/GoogleUtilities/tags)
for Swift PM, the newly tagged version will be immediately released for public use.
Given this, please verify the intended time of release for Swift PM.
* Add a version tag for Swift PM
```console
git tag {version}
git push origin {version}
```
*Note: Ensure that any inflight PRs that depend on the new `GoogleUtilities` version are updated to point to the
newly tagged version rather than a checksum.*
### CocoaPods
* Publish the newly versioned pod to CocoaPods
It's recommended to point to the `GoogleUtilities.podspec` in `staging` to make sure the correct spec is being published.
```console
pod trunk push ~/.cocoapods/repos/staging/GoogleUtilities/{version}/GoogleUtilities.podspec.json
```
*Note: In some cases, it may be acceptable to `pod trunk push` with the `--skip-tests` flag. Please double check with
the maintainers before doing so.*
The pod push was successful if the above command logs: `🚀 GoogleUtilities ({version}) successfully published`.
In addition, a new commit that publishes the new version (co-authored by [CocoaPodsAtGoogle](https://github.com/CocoaPodsAtGoogle))
should appear in the [CocoaPods specs repo](https://github.com/CocoaPods/Specs). Last, the latest version should be displayed
on [GoogleUtilities's CocoaPods page](https://cocoapods.org/pods/GoogleUtilities).
### [Create GitHub Release](https://github.com/google/GoogleUtilities/releases/new/)
Update the [release template](https://github.com/google/GoogleUtilities/releases/new/)'s **Tag version** and **Release title**
fields with the latest version. In addition, reference the [Release Notes](./CHANGELOG.md) in the release's description.
See [this release](https://github.com/google/GoogleUtilities/releases/edit/7.7.0) for an example.
*Don't forget to perform the [post release cleanup](#post-release-cleanup)!*
### Post Release Cleanup
<details>
<summary>Clean up <b>SpecsStaging</b></summary>
```console
pwd=$(pwd)
mkdir -p /tmp/release-cleanup && cd $_
git clone git@github.com:firebase/SpecsStaging.git
cd SpecsStaging/
git rm -rf GoogleUtilities/
git commit -m "Post publish cleanup"
git push origin master
rm -rf /tmp/release-cleanup
cd $pwd
```
</details>
## Development
To develop in this repository, ensure that you have at least the following software:
* Xcode 12.0 (or later)
* CocoaPods 1.10.0 (or later)
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
For the pod that you want to develop:
`pod gen GoogleUtilities.podspec --local-sources=./ --auto-open --platforms=ios`
Note: If the CocoaPods cache is out of date, you may need to run
`pod repo update` before the `pod gen` command.
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
those platforms. Since 10.2, Xcode does not properly handle multi-platform
CocoaPods workspaces.
### Development for Catalyst
* `pod gen GoogleUtilities.podspec --local-sources=./ --auto-open --platforms=ios`
* Check the Mac box in the App-iOS Build Settings
* Sign the App in the Settings Signing & Capabilities tab
* Click Pods in the Project Manager
* Add Signing to the iOS host app and unit test targets
* Select the Unit-unit scheme
* Run it to build and test
Alternatively disable signing in each target:
* Go to Build Settings tab
* Click `+`
* Select `Add User-Defined Setting`
* Add `CODE_SIGNING_REQUIRED` setting with a value of `NO`
### Code Formatting
To ensure that the code is formatted consistently, run the script
[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh)
before creating a PR.
GitHub Actions will verify that any code changes are done in a style compliant
way. Install `clang-format` and `mint`:
```console
brew install clang-format@18
brew install mint
```
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
## Contributing
See [Contributing](CONTRIBUTING.md).
## License
The contents of this repository is licensed under the
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
[gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions
[gh-google-utilities-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/google-utilities/badge.svg

View File

@ -0,0 +1,104 @@
// 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>
#import "third_party/IsAppEncrypted/Public/IsAppEncrypted.h"
#import <mach-o/dyld.h>
#import <objc/objc.h>
/// 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
BOOL IsAppEncrypted(void) {
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;
}

View File

@ -0,0 +1,24 @@
// 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.
#import <objc/objc.h>
BOOL IsAppEncrypted(void);