You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

400 lines
16 KiB

6 years ago
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FIRAuthAppDelegateProxy.h"
  17. #import <objc/runtime.h>
  18. NS_ASSUME_NONNULL_BEGIN
  19. /** @var kProxyEnabledBundleKey
  20. @brief The key in application's bundle plist for whether or not proxy should be enabled.
  21. @remarks This key is a shared constant with Analytics and FCM.
  22. */
  23. static NSString *const kProxyEnabledBundleKey = @"FirebaseAppDelegateProxyEnabled";
  24. /** @fn noop
  25. @brief A function that does nothing.
  26. @remarks This is used as the placeholder for unimplemented UApplicationDelegate methods,
  27. because once we added a method there is no way to remove it from the class.
  28. */
  29. #if !OBJC_OLD_DISPATCH_PROTOTYPES
  30. static void noop(void) {
  31. }
  32. #else
  33. static id noop(id object, SEL cmd, ...) {
  34. return nil;
  35. }
  36. #endif
  37. /** @fn isIOS9orLater
  38. @brief Checks whether the iOS version is 9 or later.
  39. @returns Whether the iOS version is 9 or later.
  40. */
  41. static BOOL isIOS9orLater() {
  42. #if defined(__IPHONE_11_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0)
  43. if (@available(iOS 9.0, *)) {
  44. return YES;
  45. }
  46. return NO;
  47. #else
  48. // UIApplicationOpenURLOptionsAnnotationKey is only available on iOS 9+.
  49. return &UIApplicationOpenURLOptionsAnnotationKey != NULL;
  50. #endif
  51. }
  52. @implementation FIRAuthAppDelegateProxy {
  53. /** @var _appDelegate
  54. @brief The application delegate whose method is being swizzled.
  55. */
  56. id<UIApplicationDelegate> _appDelegate;
  57. /** @var _orginalImplementationsBySelector
  58. @brief A map from selectors to original implementations that have been swizzled.
  59. */
  60. NSMutableDictionary<NSValue *, NSValue *> *_originalImplementationsBySelector;
  61. /** @var _handlers
  62. @brief The array of weak pointers of `id<FIRAuthAppDelegateHandler>`.
  63. */
  64. NSPointerArray *_handlers;
  65. }
  66. - (nullable instancetype)initWithApplication:(nullable UIApplication *)application {
  67. self = [super init];
  68. if (self) {
  69. id proxyEnabled = [[NSBundle mainBundle] objectForInfoDictionaryKey:kProxyEnabledBundleKey];
  70. if ([proxyEnabled isKindOfClass:[NSNumber class]] && !((NSNumber *)proxyEnabled).boolValue) {
  71. return nil;
  72. }
  73. _appDelegate = application.delegate;
  74. if (![_appDelegate conformsToProtocol:@protocol(UIApplicationDelegate)]) {
  75. return nil;
  76. }
  77. _originalImplementationsBySelector = [[NSMutableDictionary<NSValue *, NSValue *> alloc] init];
  78. _handlers = [[NSPointerArray alloc] initWithOptions:NSPointerFunctionsWeakMemory];
  79. // Swizzle the methods.
  80. __weak FIRAuthAppDelegateProxy *weakSelf = self;
  81. SEL registerDeviceTokenSelector =
  82. @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
  83. [self replaceSelector:registerDeviceTokenSelector
  84. withBlock:^(id object, UIApplication* application, NSData *deviceToken) {
  85. [weakSelf object:object
  86. selector:registerDeviceTokenSelector
  87. application:application
  88. didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
  89. }];
  90. SEL failToRegisterRemoteNotificationSelector =
  91. @selector(application:didFailToRegisterForRemoteNotificationsWithError:);
  92. [self replaceSelector:failToRegisterRemoteNotificationSelector
  93. withBlock:^(id object, UIApplication* application, NSError *error) {
  94. [weakSelf object:object
  95. selector:failToRegisterRemoteNotificationSelector
  96. application:application
  97. didFailToRegisterForRemoteNotificationsWithError:error];
  98. }];
  99. SEL receiveNotificationSelector = @selector(application:didReceiveRemoteNotification:);
  100. SEL receiveNotificationWithHandlerSelector =
  101. @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
  102. if ([_appDelegate respondsToSelector:receiveNotificationWithHandlerSelector] ||
  103. ![_appDelegate respondsToSelector:receiveNotificationSelector]) {
  104. // Replace the modern selector which is available on iOS 7 and above.
  105. [self replaceSelector:receiveNotificationWithHandlerSelector
  106. withBlock:^(id object, UIApplication *application, NSDictionary *notification,
  107. void (^completionHandler)(UIBackgroundFetchResult)) {
  108. [weakSelf object:object
  109. selector:receiveNotificationWithHandlerSelector
  110. application:application
  111. didReceiveRemoteNotification:notification
  112. fetchCompletionHandler:completionHandler];
  113. }];
  114. } else {
  115. // Replace the deprecated selector because this is the only one that the client app uses.
  116. [self replaceSelector:receiveNotificationSelector
  117. withBlock:^(id object, UIApplication *application, NSDictionary *notification) {
  118. [weakSelf object:object
  119. selector:receiveNotificationSelector
  120. application:application
  121. didReceiveRemoteNotification:notification];
  122. }];
  123. }
  124. SEL openURLOptionsSelector = @selector(application:openURL:options:);
  125. SEL openURLAnnotationSelector = @selector(application:openURL:sourceApplication:annotation:);
  126. SEL handleOpenURLSelector = @selector(application:handleOpenURL:);
  127. if (isIOS9orLater() &&
  128. ([_appDelegate respondsToSelector:openURLOptionsSelector] ||
  129. (![_appDelegate respondsToSelector:openURLAnnotationSelector] &&
  130. ![_appDelegate respondsToSelector:handleOpenURLSelector]))) {
  131. // Replace the modern selector which is avaliable on iOS 9 and above because this is the one
  132. // that the client app uses or the client app doesn't use any of them.
  133. [self replaceSelector:openURLOptionsSelector
  134. withBlock:^BOOL(id object, UIApplication *application, NSURL *url,
  135. NSDictionary *options) {
  136. return [weakSelf object:object
  137. selector:openURLOptionsSelector
  138. application:application
  139. openURL:url
  140. options:options];
  141. }];
  142. } else if ([_appDelegate respondsToSelector:openURLAnnotationSelector] ||
  143. ![_appDelegate respondsToSelector:handleOpenURLSelector]) {
  144. // Replace the longer form of the deprecated selectors on iOS 8 and below because this is the
  145. // one that the client app uses or the client app doesn't use either of the applicable ones.
  146. [self replaceSelector:openURLAnnotationSelector
  147. withBlock:^(id object, UIApplication *application, NSURL *url,
  148. NSString *sourceApplication, id annotation) {
  149. return [weakSelf object:object
  150. selector:openURLAnnotationSelector
  151. application:application
  152. openURL:url
  153. sourceApplication:sourceApplication
  154. annotation:annotation];
  155. }];
  156. } else {
  157. // Replace the shorter form of the deprecated selectors on iOS 8 and below because this is
  158. // the only one that the client app uses.
  159. [self replaceSelector:handleOpenURLSelector
  160. withBlock:^(id object, UIApplication *application, NSURL *url) {
  161. return [weakSelf object:object
  162. selector:handleOpenURLSelector
  163. application:application
  164. handleOpenURL:url];
  165. }];
  166. }
  167. // Reset the application delegate to clear the system cache that indicates whether each of the
  168. // openURL: methods is implemented on the application delegate.
  169. application.delegate = nil;
  170. application.delegate = _appDelegate;
  171. }
  172. return self;
  173. }
  174. - (void)dealloc {
  175. for (NSValue *selector in _originalImplementationsBySelector) {
  176. IMP implementation = _originalImplementationsBySelector[selector].pointerValue;
  177. Method method = class_getInstanceMethod([_appDelegate class], selector.pointerValue);
  178. imp_removeBlock(method_setImplementation(method, implementation));
  179. }
  180. }
  181. - (void)addHandler:(__weak id<FIRAuthAppDelegateHandler>)handler {
  182. @synchronized (_handlers) {
  183. [_handlers addPointer:(__bridge void *)handler];
  184. }
  185. }
  186. + (nullable instancetype)sharedInstance {
  187. static dispatch_once_t onceToken;
  188. static FIRAuthAppDelegateProxy *_Nullable sharedInstance;
  189. dispatch_once(&onceToken, ^{
  190. sharedInstance = [[self alloc] initWithApplication:[UIApplication sharedApplication]];
  191. });
  192. return sharedInstance;
  193. }
  194. #pragma mark - UIApplicationDelegate proxy methods.
  195. - (void)object:(id)object
  196. selector:(SEL)selector
  197. application:(UIApplication *)application
  198. didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  199. if (object == _appDelegate) {
  200. for (id<FIRAuthAppDelegateHandler> handler in [self handlers]) {
  201. [handler setAPNSToken:deviceToken];
  202. }
  203. }
  204. IMP originalImplementation = [self originalImplementationForSelector:selector];
  205. if (originalImplementation && originalImplementation != &noop) {
  206. typedef void (*Implmentation)(id, SEL, UIApplication*, NSData *);
  207. ((Implmentation)originalImplementation)(object, selector, application, deviceToken);
  208. }
  209. }
  210. - (void)object:(id)object
  211. selector:(SEL)selector
  212. application:(UIApplication *)application
  213. didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  214. if (object == _appDelegate) {
  215. for (id<FIRAuthAppDelegateHandler> handler in [self handlers]) {
  216. [handler handleAPNSTokenError:error];
  217. }
  218. }
  219. IMP originalImplementation = [self originalImplementationForSelector:selector];
  220. if (originalImplementation && originalImplementation != &noop) {
  221. typedef void (*Implmentation)(id, SEL, UIApplication *, NSError *);
  222. ((Implmentation)originalImplementation)(object, selector, application, error);
  223. }
  224. }
  225. - (void)object:(id)object
  226. selector:(SEL)selector
  227. application:(UIApplication *)application
  228. didReceiveRemoteNotification:(NSDictionary *)notification
  229. fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  230. if (object == _appDelegate) {
  231. for (id<FIRAuthAppDelegateHandler> handler in [self handlers]) {
  232. if ([handler canHandleNotification:notification]) {
  233. completionHandler(UIBackgroundFetchResultNoData);
  234. return;
  235. };
  236. }
  237. }
  238. IMP originalImplementation = [self originalImplementationForSelector:selector];
  239. if (originalImplementation && originalImplementation != &noop) {
  240. typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *,
  241. void (^)(UIBackgroundFetchResult));
  242. ((Implmentation)originalImplementation)(object, selector, application, notification,
  243. completionHandler);
  244. }
  245. }
  246. - (void)object:(id)object
  247. selector:(SEL)selector
  248. application:(UIApplication *)application
  249. didReceiveRemoteNotification:(NSDictionary *)notification {
  250. if (object == _appDelegate) {
  251. for (id<FIRAuthAppDelegateHandler> handler in [self handlers]) {
  252. if ([handler canHandleNotification:notification]) {
  253. return;
  254. };
  255. }
  256. }
  257. IMP originalImplementation = [self originalImplementationForSelector:selector];
  258. if (originalImplementation && originalImplementation != &noop) {
  259. typedef void (*Implmentation)(id, SEL, UIApplication*, NSDictionary *);
  260. ((Implmentation)originalImplementation)(object, selector, application, notification);
  261. }
  262. }
  263. - (BOOL)object:(id)object
  264. selector:(SEL)selector
  265. application:(UIApplication *)application
  266. openURL:(NSURL *)url
  267. options:(NSDictionary *)options {
  268. if (object == _appDelegate && [self delegateCanHandleURL:url]) {
  269. return YES;
  270. }
  271. IMP originalImplementation = [self originalImplementationForSelector:selector];
  272. if (originalImplementation && originalImplementation != &noop) {
  273. typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSDictionary *);
  274. return ((Implmentation)originalImplementation)(object, selector, application, url, options);
  275. }
  276. return NO;
  277. }
  278. - (BOOL)object:(id)object
  279. selector:(SEL)selector
  280. application:(UIApplication *)application
  281. openURL:(NSURL *)url
  282. sourceApplication:(NSString *)sourceApplication
  283. annotation:(id)annotation {
  284. if (object == _appDelegate && [self delegateCanHandleURL:url]) {
  285. return YES;
  286. }
  287. IMP originalImplementation = [self originalImplementationForSelector:selector];
  288. if (originalImplementation && originalImplementation != &noop) {
  289. typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *, NSString *, id);
  290. return ((Implmentation)originalImplementation)(object, selector, application, url,
  291. sourceApplication, annotation);
  292. }
  293. return NO;
  294. }
  295. - (BOOL)object:(id)object
  296. selector:(SEL)selector
  297. application:(UIApplication *)application
  298. handleOpenURL:(NSURL *)url {
  299. if (object == _appDelegate && [self delegateCanHandleURL:url]) {
  300. return YES;
  301. }
  302. IMP originalImplementation = [self originalImplementationForSelector:selector];
  303. if (originalImplementation && originalImplementation != &noop) {
  304. typedef BOOL (*Implmentation)(id, SEL, UIApplication*, NSURL *);
  305. return ((Implmentation)originalImplementation)(object, selector, application, url);
  306. }
  307. return NO;
  308. }
  309. #pragma mark - Internal Methods
  310. /** @fn delegateCanHandleURL:
  311. @brief Checks for whether any of the delegates can handle the URL.
  312. @param url The URL in question.
  313. @return Whether any of the delegate can handle the URL.
  314. */
  315. - (BOOL)delegateCanHandleURL:(NSURL *)url {
  316. for (id<FIRAuthAppDelegateHandler> handler in [self handlers]) {
  317. if ([handler canHandleURL:url]) {
  318. return YES;
  319. };
  320. }
  321. return NO;
  322. }
  323. /** @fn handlers
  324. @brief Gets the list of handlers from `_handlers` safely.
  325. */
  326. - (NSArray<id<FIRAuthAppDelegateHandler>> *)handlers {
  327. @synchronized (_handlers) {
  328. NSMutableArray<id<FIRAuthAppDelegateHandler>> *liveHandlers =
  329. [[NSMutableArray<id<FIRAuthAppDelegateHandler>> alloc] initWithCapacity:_handlers.count];
  330. for (__weak id<FIRAuthAppDelegateHandler> handler in _handlers) {
  331. if (handler) {
  332. [liveHandlers addObject:handler];
  333. }
  334. }
  335. if (liveHandlers.count < _handlers.count) {
  336. [_handlers compact];
  337. }
  338. return liveHandlers;
  339. }
  340. }
  341. /** @fn replaceSelector:withBlock:
  342. @brief replaces the implementation for a method of `_appDelegate` specified by a selector.
  343. @param selector The selector for the method.
  344. @param block The block as the new implementation of the method.
  345. */
  346. - (void)replaceSelector:(SEL)selector withBlock:(id)block {
  347. Method originalMethod = class_getInstanceMethod([_appDelegate class], selector);
  348. IMP newImplementation = imp_implementationWithBlock(block);
  349. IMP originalImplementation;
  350. if (originalMethod) {
  351. originalImplementation = method_setImplementation(originalMethod, newImplementation) ?: &noop;
  352. } else {
  353. // The original method was not implemented in the class, add it with the new implementation.
  354. struct objc_method_description methodDescription =
  355. protocol_getMethodDescription(@protocol(UIApplicationDelegate), selector, NO, YES);
  356. class_addMethod([_appDelegate class], selector, newImplementation, methodDescription.types);
  357. originalImplementation = &noop;
  358. }
  359. _originalImplementationsBySelector[[NSValue valueWithPointer:selector]] =
  360. [NSValue valueWithPointer:originalImplementation];
  361. }
  362. /** @fn originalImplementationForSelector:
  363. @brief Gets the original implementation for the given selector.
  364. @param selector The selector for the method that has been replaced.
  365. @return The original implementation if there was one.
  366. */
  367. - (IMP)originalImplementationForSelector:(SEL)selector {
  368. return _originalImplementationsBySelector[[NSValue valueWithPointer:selector]].pointerValue;
  369. }
  370. @end
  371. NS_ASSUME_NONNULL_END