暂无描述

FIRApp.m 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836
  1. // Copyright 2017 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #include <sys/utsname.h>
  15. #if __has_include(<UIKit/UIKit.h>)
  16. #import <UIKit/UIKit.h>
  17. #endif
  18. #if __has_include(<AppKit/AppKit.h>)
  19. #import <AppKit/AppKit.h>
  20. #endif
  21. #import "FIRApp.h"
  22. #import "Private/FIRAnalyticsConfiguration.h"
  23. #import "Private/FIRAppInternal.h"
  24. #import "Private/FIRBundleUtil.h"
  25. #import "Private/FIRComponentContainerInternal.h"
  26. #import "Private/FIRConfigurationInternal.h"
  27. #import "Private/FIRCoreDiagnosticsConnector.h"
  28. #import "Private/FIRLibrary.h"
  29. #import "Private/FIRLogger.h"
  30. #import "Private/FIROptionsInternal.h"
  31. // The kFIRService strings are only here while transitioning CoreDiagnostics from the Analytics
  32. // pod to a Core dependency. These symbols are not used and should be deleted after the transition.
  33. NSString *const kFIRServiceAdMob;
  34. NSString *const kFIRServiceAuth;
  35. NSString *const kFIRServiceAuthUI;
  36. NSString *const kFIRServiceCrash;
  37. NSString *const kFIRServiceDatabase;
  38. NSString *const kFIRServiceDynamicLinks;
  39. NSString *const kFIRServiceFirestore;
  40. NSString *const kFIRServiceFunctions;
  41. NSString *const kFIRServiceInstanceID;
  42. NSString *const kFIRServiceInvites;
  43. NSString *const kFIRServiceMessaging;
  44. NSString *const kFIRServiceMeasurement;
  45. NSString *const kFIRServicePerformance;
  46. NSString *const kFIRServiceRemoteConfig;
  47. NSString *const kFIRServiceStorage;
  48. NSString *const kGGLServiceAnalytics;
  49. NSString *const kGGLServiceSignIn;
  50. NSString *const kFIRDefaultAppName = @"__FIRAPP_DEFAULT";
  51. NSString *const kFIRAppReadyToConfigureSDKNotification = @"FIRAppReadyToConfigureSDKNotification";
  52. NSString *const kFIRAppDeleteNotification = @"FIRAppDeleteNotification";
  53. NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey";
  54. NSString *const kFIRAppNameKey = @"FIRAppNameKey";
  55. NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey";
  56. NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat =
  57. @"/google/firebase/global_data_collection_enabled:%@";
  58. NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey =
  59. @"FirebaseDataCollectionDefaultEnabled";
  60. NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification";
  61. NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
  62. NSString *const kFIRAppDiagnosticsErrorKey = @"Error";
  63. NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp";
  64. NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName";
  65. NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion";
  66. // Auth internal notification notification and key.
  67. NSString *const FIRAuthStateDidChangeInternalNotification =
  68. @"FIRAuthStateDidChangeInternalNotification";
  69. NSString *const FIRAuthStateDidChangeInternalNotificationAppKey =
  70. @"FIRAuthStateDidChangeInternalNotificationAppKey";
  71. NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey =
  72. @"FIRAuthStateDidChangeInternalNotificationTokenKey";
  73. NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey =
  74. @"FIRAuthStateDidChangeInternalNotificationUIDKey";
  75. /**
  76. * The URL to download plist files.
  77. */
  78. static NSString *const kPlistURL = @"https://console.firebase.google.com/";
  79. /**
  80. * An array of all classes that registered as `FIRCoreConfigurable` in order to receive lifecycle
  81. * events from Core.
  82. */
  83. static NSMutableArray<Class<FIRLibrary>> *sRegisteredAsConfigurable;
  84. @interface FIRApp ()
  85. #ifdef DEBUG
  86. @property(nonatomic) BOOL alreadyOutputDataCollectionFlag;
  87. #endif // DEBUG
  88. @end
  89. @implementation FIRApp
  90. // This is necessary since our custom getter prevents `_options` from being created.
  91. @synthesize options = _options;
  92. static NSMutableDictionary *sAllApps;
  93. static FIRApp *sDefaultApp;
  94. static NSMutableDictionary *sLibraryVersions;
  95. + (void)configure {
  96. FIROptions *options = [FIROptions defaultOptions];
  97. if (!options) {
  98. [NSException raise:kFirebaseCoreErrorDomain
  99. format:@"`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) could not find "
  100. @"a valid GoogleService-Info.plist in your project. Please download one "
  101. @"from %@.",
  102. kPlistURL];
  103. }
  104. [FIRApp configureWithOptions:options];
  105. #if TARGET_OS_OSX || TARGET_OS_TV
  106. FIRLogNotice(kFIRLoggerCore, @"I-COR000028",
  107. @"tvOS and macOS SDK support is not part of the official Firebase product. "
  108. @"Instead they are community supported. Details at "
  109. @"https://github.com/firebase/firebase-ios-sdk/blob/master/README.md.");
  110. #endif
  111. }
  112. + (void)configureWithOptions:(FIROptions *)options {
  113. if (!options) {
  114. [NSException raise:kFirebaseCoreErrorDomain
  115. format:@"Options is nil. Please pass a valid options."];
  116. }
  117. [FIRApp configureWithName:kFIRDefaultAppName options:options];
  118. }
  119. + (NSCharacterSet *)applicationNameAllowedCharacters {
  120. static NSCharacterSet *applicationNameAllowedCharacters;
  121. static dispatch_once_t onceToken;
  122. dispatch_once(&onceToken, ^{
  123. NSMutableCharacterSet *allowedNameCharacters = [NSMutableCharacterSet alphanumericCharacterSet];
  124. [allowedNameCharacters addCharactersInString:@"-_"];
  125. applicationNameAllowedCharacters = [allowedNameCharacters copy];
  126. });
  127. return applicationNameAllowedCharacters;
  128. }
  129. + (void)configureWithName:(NSString *)name options:(FIROptions *)options {
  130. if (!name || !options) {
  131. [NSException raise:kFirebaseCoreErrorDomain format:@"Neither name nor options can be nil."];
  132. }
  133. if (name.length == 0) {
  134. [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be empty."];
  135. }
  136. if ([name isEqualToString:kFIRDefaultAppName]) {
  137. if (sDefaultApp) {
  138. [NSException raise:kFirebaseCoreErrorDomain
  139. format:@"Default app has already been configured."];
  140. }
  141. FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app.");
  142. } else {
  143. // Validate the app name and ensure it hasn't been configured already.
  144. NSCharacterSet *nameCharacters = [NSCharacterSet characterSetWithCharactersInString:name];
  145. if (![[self applicationNameAllowedCharacters] isSupersetOfSet:nameCharacters]) {
  146. [NSException raise:kFirebaseCoreErrorDomain
  147. format:@"App name can only contain alphanumeric, "
  148. @"hyphen (-), and underscore (_) characters"];
  149. }
  150. @synchronized(self) {
  151. if (sAllApps && sAllApps[name]) {
  152. [NSException raise:kFirebaseCoreErrorDomain
  153. format:@"App named %@ has already been configured.", name];
  154. }
  155. }
  156. FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name);
  157. }
  158. @synchronized(self) {
  159. FIRApp *app = [[FIRApp alloc] initInstanceWithName:name options:options];
  160. if (app.isDefaultApp) {
  161. sDefaultApp = app;
  162. }
  163. [FIRApp addAppToAppDictionary:app];
  164. [FIRApp sendNotificationsToSDKs:app];
  165. }
  166. }
  167. + (FIRApp *)defaultApp {
  168. if (sDefaultApp) {
  169. return sDefaultApp;
  170. }
  171. FIRLogError(kFIRLoggerCore, @"I-COR000003",
  172. @"The default Firebase app has not yet been "
  173. @"configured. Add `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) to your "
  174. @"application initialization. Read more: https://goo.gl/ctyzm8.");
  175. return nil;
  176. }
  177. + (FIRApp *)appNamed:(NSString *)name {
  178. @synchronized(self) {
  179. if (sAllApps) {
  180. FIRApp *app = sAllApps[name];
  181. if (app) {
  182. return app;
  183. }
  184. }
  185. FIRLogError(kFIRLoggerCore, @"I-COR000004", @"App with name %@ does not exist.", name);
  186. return nil;
  187. }
  188. }
  189. + (NSDictionary *)allApps {
  190. @synchronized(self) {
  191. if (!sAllApps) {
  192. FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet.");
  193. }
  194. return [sAllApps copy];
  195. }
  196. }
  197. // Public only for tests
  198. + (void)resetApps {
  199. @synchronized(self) {
  200. sDefaultApp = nil;
  201. [sAllApps removeAllObjects];
  202. sAllApps = nil;
  203. [sLibraryVersions removeAllObjects];
  204. sLibraryVersions = nil;
  205. }
  206. }
  207. - (void)deleteApp:(FIRAppVoidBoolCallback)completion {
  208. @synchronized([self class]) {
  209. if (sAllApps && sAllApps[self.name]) {
  210. FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
  211. // Remove all cached instances from the container before deleting the app.
  212. [self.container removeAllCachedInstances];
  213. [sAllApps removeObjectForKey:self.name];
  214. [self clearDataCollectionSwitchFromUserDefaults];
  215. if ([self.name isEqualToString:kFIRDefaultAppName]) {
  216. sDefaultApp = nil;
  217. }
  218. NSDictionary *appInfoDict = @{kFIRAppNameKey : self.name};
  219. [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDeleteNotification
  220. object:[self class]
  221. userInfo:appInfoDict];
  222. completion(YES);
  223. } else {
  224. FIRLogError(kFIRLoggerCore, @"I-COR000007", @"App does not exist.");
  225. completion(NO);
  226. }
  227. }
  228. }
  229. + (void)addAppToAppDictionary:(FIRApp *)app {
  230. if (!sAllApps) {
  231. sAllApps = [NSMutableDictionary dictionary];
  232. }
  233. if ([app configureCore]) {
  234. sAllApps[app.name] = app;
  235. } else {
  236. [NSException raise:kFirebaseCoreErrorDomain
  237. format:@"Configuration fails. It may be caused by an invalid GOOGLE_APP_ID in "
  238. @"GoogleService-Info.plist or set in the customized options."];
  239. }
  240. }
  241. - (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options {
  242. self = [super init];
  243. if (self) {
  244. _name = [name copy];
  245. _options = [options copy];
  246. _options.editingLocked = YES;
  247. _isDefaultApp = [name isEqualToString:kFIRDefaultAppName];
  248. _container = [[FIRComponentContainer alloc] initWithApp:self];
  249. }
  250. return self;
  251. }
  252. - (void)dealloc {
  253. [[NSNotificationCenter defaultCenter] removeObserver:self];
  254. }
  255. - (BOOL)configureCore {
  256. [self checkExpectedBundleID];
  257. if (![self isAppIDValid]) {
  258. return NO;
  259. }
  260. [self logCoreTelemetryIfEnabled];
  261. #if TARGET_OS_IOS
  262. // Initialize the Analytics once there is a valid options under default app. Analytics should
  263. // always initialize first by itself before the other SDKs.
  264. if ([self.name isEqualToString:kFIRDefaultAppName]) {
  265. Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics");
  266. if (firAnalyticsClass) {
  267. #pragma clang diagnostic push
  268. #pragma clang diagnostic ignored "-Wundeclared-selector"
  269. SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:);
  270. #pragma clang diagnostic pop
  271. if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) {
  272. #pragma clang diagnostic push
  273. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  274. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  275. [firAnalyticsClass performSelector:startWithConfigurationSelector
  276. withObject:[FIRConfiguration sharedInstance].analyticsConfiguration
  277. withObject:_options];
  278. #pragma clang diagnostic pop
  279. }
  280. }
  281. }
  282. #endif
  283. [self subscribeForAppDidBecomeActiveNotifications];
  284. return YES;
  285. }
  286. - (FIROptions *)options {
  287. return [_options copy];
  288. }
  289. - (void)setDataCollectionDefaultEnabled:(BOOL)dataCollectionDefaultEnabled {
  290. #ifdef DEBUG
  291. FIRLogDebug(kFIRLoggerCore, @"I-COR000034", @"Explicitly %@ data collection flag.",
  292. dataCollectionDefaultEnabled ? @"enabled" : @"disabled");
  293. self.alreadyOutputDataCollectionFlag = YES;
  294. #endif // DEBUG
  295. NSString *key =
  296. [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
  297. [[NSUserDefaults standardUserDefaults] setBool:dataCollectionDefaultEnabled forKey:key];
  298. // Core also controls the FirebaseAnalytics flag, so check if the Analytics flags are set
  299. // within FIROptions and change the Analytics value if necessary. Analytics only works with the
  300. // default app, so return if this isn't the default app.
  301. if (!self.isDefaultApp) {
  302. return;
  303. }
  304. // Check if the Analytics flag is explicitly set. If so, no further actions are necessary.
  305. if ([self.options isAnalyticsCollectionExplicitlySet]) {
  306. return;
  307. }
  308. // The Analytics flag has not been explicitly set, so update with the value being set.
  309. #pragma clang diagnostic push
  310. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  311. [[FIRAnalyticsConfiguration sharedInstance]
  312. setAnalyticsCollectionEnabled:dataCollectionDefaultEnabled
  313. persistSetting:NO];
  314. #pragma clang diagnostic pop
  315. }
  316. - (BOOL)isDataCollectionDefaultEnabled {
  317. // Check if it's been manually set before in code, and use that as the higher priority value.
  318. NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self];
  319. if (defaultsObject != nil) {
  320. #ifdef DEBUG
  321. if (!self.alreadyOutputDataCollectionFlag) {
  322. FIRLogDebug(kFIRLoggerCore, @"I-COR000031", @"Data Collection flag is %@ in user defaults.",
  323. [defaultsObject boolValue] ? @"enabled" : @"disabled");
  324. self.alreadyOutputDataCollectionFlag = YES;
  325. }
  326. #endif // DEBUG
  327. return [defaultsObject boolValue];
  328. }
  329. // Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`.
  330. // As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has
  331. // no performance impact calling multiple times.
  332. NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist];
  333. if (collectionEnabledPlistValue != nil) {
  334. #ifdef DEBUG
  335. if (!self.alreadyOutputDataCollectionFlag) {
  336. FIRLogDebug(kFIRLoggerCore, @"I-COR000032", @"Data Collection flag is %@ in plist.",
  337. [collectionEnabledPlistValue boolValue] ? @"enabled" : @"disabled");
  338. self.alreadyOutputDataCollectionFlag = YES;
  339. }
  340. #endif // DEBUG
  341. return [collectionEnabledPlistValue boolValue];
  342. }
  343. #ifdef DEBUG
  344. if (!self.alreadyOutputDataCollectionFlag) {
  345. FIRLogDebug(kFIRLoggerCore, @"I-COR000033", @"Data Collection flag is not set.");
  346. self.alreadyOutputDataCollectionFlag = YES;
  347. }
  348. #endif // DEBUG
  349. return YES;
  350. }
  351. #pragma mark - private
  352. + (void)sendNotificationsToSDKs:(FIRApp *)app {
  353. // TODO: Remove this notification once all SDKs are registered with `FIRCoreConfigurable`.
  354. NSNumber *isDefaultApp = [NSNumber numberWithBool:app.isDefaultApp];
  355. NSDictionary *appInfoDict = @{
  356. kFIRAppNameKey : app.name,
  357. kFIRAppIsDefaultAppKey : isDefaultApp,
  358. kFIRGoogleAppIDKey : app.options.googleAppID
  359. };
  360. [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification
  361. object:self
  362. userInfo:appInfoDict];
  363. // This is the new way of sending information to SDKs.
  364. // TODO: Do we want this on a background thread, maybe?
  365. @synchronized(self) {
  366. for (Class<FIRLibrary> library in sRegisteredAsConfigurable) {
  367. [library configureWithApp:app];
  368. }
  369. }
  370. }
  371. + (NSError *)errorForMissingOptions {
  372. NSDictionary *errorDict = @{
  373. NSLocalizedDescriptionKey :
  374. @"Unable to parse GoogleService-Info.plist in order to configure services.",
  375. NSLocalizedRecoverySuggestionErrorKey :
  376. @"Check formatting and location of GoogleService-Info.plist."
  377. };
  378. return [NSError errorWithDomain:kFirebaseCoreErrorDomain
  379. code:FIRErrorCodeInvalidPlistFile
  380. userInfo:errorDict];
  381. }
  382. + (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain
  383. errorCode:(FIRErrorCode)code
  384. service:(NSString *)service
  385. reason:(NSString *)reason {
  386. NSString *description =
  387. [NSString stringWithFormat:@"Configuration failed for service %@.", service];
  388. NSDictionary *errorDict =
  389. @{NSLocalizedDescriptionKey : description, NSLocalizedFailureReasonErrorKey : reason};
  390. return [NSError errorWithDomain:domain code:code userInfo:errorDict];
  391. }
  392. + (NSError *)errorForInvalidAppID {
  393. NSDictionary *errorDict = @{
  394. NSLocalizedDescriptionKey : @"Unable to validate Google App ID",
  395. NSLocalizedRecoverySuggestionErrorKey :
  396. @"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the "
  397. @"customized options."
  398. };
  399. return [NSError errorWithDomain:kFirebaseCoreErrorDomain
  400. code:FIRErrorCodeInvalidAppID
  401. userInfo:errorDict];
  402. }
  403. + (BOOL)isDefaultAppConfigured {
  404. return (sDefaultApp != nil);
  405. }
  406. + (void)registerLibrary:(nonnull NSString *)name withVersion:(nonnull NSString *)version {
  407. // Create the set of characters which aren't allowed, only if this feature is used.
  408. NSMutableCharacterSet *allowedSet = [NSMutableCharacterSet alphanumericCharacterSet];
  409. [allowedSet addCharactersInString:@"-_."];
  410. NSCharacterSet *disallowedSet = [allowedSet invertedSet];
  411. // Make sure the library name and version strings do not contain unexpected characters, and
  412. // add the name/version pair to the dictionary.
  413. if ([name rangeOfCharacterFromSet:disallowedSet].location == NSNotFound &&
  414. [version rangeOfCharacterFromSet:disallowedSet].location == NSNotFound) {
  415. @synchronized(self) {
  416. if (!sLibraryVersions) {
  417. sLibraryVersions = [[NSMutableDictionary alloc] init];
  418. }
  419. sLibraryVersions[name] = version;
  420. }
  421. } else {
  422. FIRLogError(kFIRLoggerCore, @"I-COR000027",
  423. @"The library name (%@) or version number (%@) contain invalid characters. "
  424. @"Only alphanumeric, dash, underscore and period characters are allowed.",
  425. name, version);
  426. }
  427. }
  428. + (void)registerInternalLibrary:(nonnull Class<FIRLibrary>)library
  429. withName:(nonnull NSString *)name
  430. withVersion:(nonnull NSString *)version {
  431. // This is called at +load time, keep the work to a minimum.
  432. // Ensure the class given conforms to the proper protocol.
  433. if (![(Class)library conformsToProtocol:@protocol(FIRLibrary)] ||
  434. ![(Class)library respondsToSelector:@selector(componentsToRegister)]) {
  435. [NSException raise:NSInvalidArgumentException
  436. format:@"Class %@ attempted to register components, but it does not conform to "
  437. @"`FIRLibrary or provide a `componentsToRegister:` method.",
  438. library];
  439. }
  440. [FIRComponentContainer registerAsComponentRegistrant:library];
  441. if ([(Class)library respondsToSelector:@selector(configureWithApp:)]) {
  442. static dispatch_once_t onceToken;
  443. dispatch_once(&onceToken, ^{
  444. sRegisteredAsConfigurable = [[NSMutableArray alloc] init];
  445. });
  446. @synchronized(self) {
  447. [sRegisteredAsConfigurable addObject:library];
  448. }
  449. }
  450. [self registerLibrary:name withVersion:version];
  451. }
  452. + (NSString *)firebaseUserAgent {
  453. @synchronized(self) {
  454. NSMutableArray<NSString *> *libraries =
  455. [[NSMutableArray<NSString *> alloc] initWithCapacity:sLibraryVersions.count];
  456. for (NSString *libraryName in sLibraryVersions) {
  457. [libraries addObject:[NSString stringWithFormat:@"%@/%@", libraryName,
  458. sLibraryVersions[libraryName]]];
  459. }
  460. [libraries sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
  461. return [libraries componentsJoinedByString:@" "];
  462. }
  463. }
  464. - (void)checkExpectedBundleID {
  465. NSArray *bundles = [FIRBundleUtil relevantBundles];
  466. NSString *expectedBundleID = [self expectedBundleID];
  467. // The checking is only done when the bundle ID is provided in the serviceInfo dictionary for
  468. // backward compatibility.
  469. if (expectedBundleID != nil && ![FIRBundleUtil hasBundleIdentifierPrefix:expectedBundleID
  470. inBundles:bundles]) {
  471. FIRLogError(kFIRLoggerCore, @"I-COR000008",
  472. @"The project's Bundle ID is inconsistent with "
  473. @"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are "
  474. @"using a customized options. To ensure that everything can be configured "
  475. @"correctly, you may need to make the Bundle IDs consistent. To continue with this "
  476. @"plist file, you may change your app's bundle identifier to '%@'. Or you can "
  477. @"download a new configuration file that matches your bundle identifier from %@ "
  478. @"and replace the current one.",
  479. kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
  480. }
  481. }
  482. #pragma mark - private - App ID Validation
  483. /**
  484. * Validates the format and fingerprint of the app ID contained in GOOGLE_APP_ID in the plist file.
  485. * This is the main method for validating app ID.
  486. *
  487. * @return YES if the app ID fulfills the expected format and fingerprint, NO otherwise.
  488. */
  489. - (BOOL)isAppIDValid {
  490. NSString *appID = _options.googleAppID;
  491. BOOL isValid = [FIRApp validateAppID:appID];
  492. if (!isValid) {
  493. NSString *expectedBundleID = [self expectedBundleID];
  494. FIRLogError(kFIRLoggerCore, @"I-COR000009",
  495. @"The GOOGLE_APP_ID either in the plist file "
  496. @"'%@.%@' or the one set in the customized options is invalid. If you are using "
  497. @"the plist file, use the iOS version of bundle identifier to download the file, "
  498. @"and do not manually edit the GOOGLE_APP_ID. You may change your app's bundle "
  499. @"identifier to '%@'. Or you can download a new configuration file that matches "
  500. @"your bundle identifier from %@ and replace the current one.",
  501. kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
  502. };
  503. return isValid;
  504. }
  505. + (BOOL)validateAppID:(NSString *)appID {
  506. // Failing validation only occurs when we are sure we are looking at a V2 app ID and it does not
  507. // have a valid fingerprint, otherwise we just warn about the potential issue.
  508. if (!appID.length) {
  509. return NO;
  510. }
  511. NSScanner *stringScanner = [NSScanner scannerWithString:appID];
  512. stringScanner.charactersToBeSkipped = nil;
  513. NSString *appIDVersion;
  514. if (![stringScanner scanCharactersFromSet:[NSCharacterSet decimalDigitCharacterSet]
  515. intoString:&appIDVersion]) {
  516. return NO;
  517. }
  518. if (![stringScanner scanString:@":" intoString:NULL]) {
  519. // appIDVersion must be separated by ":"
  520. return NO;
  521. }
  522. NSArray *knownVersions = @[ @"1" ];
  523. if (![knownVersions containsObject:appIDVersion]) {
  524. // Permit unknown yet properly formatted app ID versions.
  525. FIRLogInfo(kFIRLoggerCore, @"I-COR000010", @"Unknown GOOGLE_APP_ID version: %@", appIDVersion);
  526. return YES;
  527. }
  528. if (![self validateAppIDFormat:appID withVersion:appIDVersion]) {
  529. return NO;
  530. }
  531. if (![self validateAppIDFingerprint:appID withVersion:appIDVersion]) {
  532. return NO;
  533. }
  534. return YES;
  535. }
  536. + (NSString *)actualBundleID {
  537. return [[NSBundle mainBundle] bundleIdentifier];
  538. }
  539. /**
  540. * Validates that the format of the app ID string is what is expected based on the supplied version.
  541. * The version must end in ":".
  542. *
  543. * For v1 app ids the format is expected to be
  544. * '<version #>:<project number>:ios:<fingerprint of bundle id>'.
  545. *
  546. * This method does not verify that the contents of the app id are correct, just that they fulfill
  547. * the expected format.
  548. *
  549. * @param appID Contents of GOOGLE_APP_ID from the plist file.
  550. * @param version Indicates what version of the app id format this string should be.
  551. * @return YES if provided string fufills the expected format, NO otherwise.
  552. */
  553. + (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version {
  554. if (!appID.length || !version.length) {
  555. return NO;
  556. }
  557. NSScanner *stringScanner = [NSScanner scannerWithString:appID];
  558. stringScanner.charactersToBeSkipped = nil;
  559. // Skip version part
  560. // '*<version #>*:<project number>:ios:<fingerprint of bundle id>'
  561. if (![stringScanner scanString:version intoString:NULL]) {
  562. // The version part is missing or mismatched
  563. return NO;
  564. }
  565. // Validate version part (see part between '*' symbols below)
  566. // '<version #>*:*<project number>:ios:<fingerprint of bundle id>'
  567. if (![stringScanner scanString:@":" intoString:NULL]) {
  568. // appIDVersion must be separated by ":"
  569. return NO;
  570. }
  571. // Validate version part (see part between '*' symbols below)
  572. // '<version #>:*<project number>*:ios:<fingerprint of bundle id>'.
  573. NSInteger projectNumber = NSNotFound;
  574. if (![stringScanner scanInteger:&projectNumber]) {
  575. // NO project number found.
  576. return NO;
  577. }
  578. // Validate version part (see part between '*' symbols below)
  579. // '<version #>:<project number>*:*ios:<fingerprint of bundle id>'.
  580. if (![stringScanner scanString:@":" intoString:NULL]) {
  581. // The project number must be separated by ":"
  582. return NO;
  583. }
  584. // Validate version part (see part between '*' symbols below)
  585. // '<version #>:<project number>:*ios*:<fingerprint of bundle id>'.
  586. NSString *platform;
  587. if (![stringScanner scanUpToString:@":" intoString:&platform]) {
  588. return NO;
  589. }
  590. if (![platform isEqualToString:@"ios"]) {
  591. // The platform must be @"ios"
  592. return NO;
  593. }
  594. // Validate version part (see part between '*' symbols below)
  595. // '<version #>:<project number>:ios*:*<fingerprint of bundle id>'.
  596. if (![stringScanner scanString:@":" intoString:NULL]) {
  597. // The platform must be separated by ":"
  598. return NO;
  599. }
  600. // Validate version part (see part between '*' symbols below)
  601. // '<version #>:<project number>:ios:*<fingerprint of bundle id>*'.
  602. unsigned long long fingerprint = NSNotFound;
  603. if (![stringScanner scanHexLongLong:&fingerprint]) {
  604. // Fingerprint part is missing
  605. return NO;
  606. }
  607. if (!stringScanner.isAtEnd) {
  608. // There are not allowed characters in the fingerprint part
  609. return NO;
  610. }
  611. return YES;
  612. }
  613. /**
  614. * Validates that the fingerprint of the app ID string is what is expected based on the supplied
  615. * version.
  616. *
  617. * Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated.
  618. *
  619. * @param appID Contents of GOOGLE_APP_ID from the plist file.
  620. * @param version Indicates what version of the app id format this string should be.
  621. * @return YES if provided string fufills the expected fingerprint and the version is known, NO
  622. * otherwise.
  623. */
  624. + (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version {
  625. // Extract the supplied fingerprint from the supplied app ID.
  626. // This assumes the app ID format is the same for all known versions below. If the app ID format
  627. // changes in future versions, the tokenizing of the app ID format will need to take into account
  628. // the version of the app ID.
  629. NSArray *components = [appID componentsSeparatedByString:@":"];
  630. if (components.count != 4) {
  631. return NO;
  632. }
  633. NSString *suppliedFingerprintString = components[3];
  634. if (!suppliedFingerprintString.length) {
  635. return NO;
  636. }
  637. uint64_t suppliedFingerprint;
  638. NSScanner *scanner = [NSScanner scannerWithString:suppliedFingerprintString];
  639. if (![scanner scanHexLongLong:&suppliedFingerprint]) {
  640. return NO;
  641. }
  642. if ([version isEqual:@"1"]) {
  643. // The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated.
  644. return YES;
  645. }
  646. // Unknown version.
  647. return NO;
  648. }
  649. - (NSString *)expectedBundleID {
  650. return _options.bundleID;
  651. }
  652. // end App ID validation
  653. #pragma mark - Reading From Plist & User Defaults
  654. /**
  655. * Clears the data collection switch from the standard NSUserDefaults for easier testing and
  656. * readability.
  657. */
  658. - (void)clearDataCollectionSwitchFromUserDefaults {
  659. NSString *key =
  660. [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
  661. [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
  662. }
  663. /**
  664. * Reads the data collection switch from the standard NSUserDefaults for easier testing and
  665. * readability.
  666. */
  667. + (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app {
  668. // Read the object in user defaults, and only return if it's an NSNumber.
  669. NSString *key =
  670. [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
  671. id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
  672. if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) {
  673. return collectionEnabledDefaultsObject;
  674. }
  675. return nil;
  676. }
  677. /**
  678. * Reads the data collection switch from the Info.plist for easier testing and readability. Will
  679. * only read once from the plist and return the cached value.
  680. */
  681. + (nullable NSNumber *)readDataCollectionSwitchFromPlist {
  682. static NSNumber *collectionEnabledPlistObject;
  683. static dispatch_once_t onceToken;
  684. dispatch_once(&onceToken, ^{
  685. // Read the data from the `Info.plist`, only assign it if it's there and an NSNumber.
  686. id plistValue = [[NSBundle mainBundle]
  687. objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey];
  688. if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) {
  689. collectionEnabledPlistObject = (NSNumber *)plistValue;
  690. }
  691. });
  692. return collectionEnabledPlistObject;
  693. }
  694. #pragma mark - Sending Logs
  695. #pragma clang diagnostic push
  696. #pragma clang diagnostic ignored "-Wunused-parameter"
  697. - (void)sendLogsWithServiceName:(NSString *)serviceName
  698. version:(NSString *)version
  699. error:(NSError *)error {
  700. // Do nothing. Please remove calls to this method.
  701. }
  702. #pragma clang diagnostic pop
  703. #pragma mark - App Life Cycle
  704. - (void)subscribeForAppDidBecomeActiveNotifications {
  705. #if TARGET_OS_IOS || TARGET_OS_TV
  706. NSNotificationName notificationName = UIApplicationDidBecomeActiveNotification;
  707. #endif
  708. #if TARGET_OS_OSX
  709. NSNotificationName notificationName = NSApplicationDidBecomeActiveNotification;
  710. #endif
  711. [[NSNotificationCenter defaultCenter] addObserver:self
  712. selector:@selector(appDidBecomeActive:)
  713. name:notificationName
  714. object:nil];
  715. }
  716. - (void)appDidBecomeActive:(NSNotification *)notification {
  717. [self logCoreTelemetryIfEnabled];
  718. }
  719. - (void)logCoreTelemetryIfEnabled {
  720. if ([self isDataCollectionDefaultEnabled]) {
  721. [FIRCoreDiagnosticsConnector logCoreTelemetryWithOptions:_options];
  722. }
  723. }
  724. @end