FIRApp.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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. #import "FIRApp.h"
  16. #import "FIRConfiguration.h"
  17. #import "Private/FIRAppInternal.h"
  18. #import "Private/FIRBundleUtil.h"
  19. #import "Private/FIRLogger.h"
  20. #import "Private/FIROptionsInternal.h"
  21. #import "third_party/FIRAppEnvironmentUtil.h"
  22. NSString *const kFIRServiceAdMob = @"AdMob";
  23. NSString *const kFIRServiceAuth = @"Auth";
  24. NSString *const kFIRServiceAuthUI = @"AuthUI";
  25. NSString *const kFIRServiceCrash = @"Crash";
  26. NSString *const kFIRServiceDatabase = @"Database";
  27. NSString *const kFIRServiceDynamicLinks = @"DynamicLinks";
  28. NSString *const kFIRServiceFirestore = @"Firestore";
  29. NSString *const kFIRServiceInstanceID = @"InstanceID";
  30. NSString *const kFIRServiceInvites = @"Invites";
  31. NSString *const kFIRServiceMessaging = @"Messaging";
  32. NSString *const kFIRServiceMeasurement = @"Measurement";
  33. NSString *const kFIRServicePerformance = @"Performance";
  34. NSString *const kFIRServiceRemoteConfig = @"RemoteConfig";
  35. NSString *const kFIRServiceStorage = @"Storage";
  36. NSString *const kGGLServiceAnalytics = @"Analytics";
  37. NSString *const kGGLServiceSignIn = @"SignIn";
  38. NSString *const kFIRDefaultAppName = @"__FIRAPP_DEFAULT";
  39. NSString *const kFIRAppReadyToConfigureSDKNotification = @"FIRAppReadyToConfigureSDKNotification";
  40. NSString *const kFIRAppDeleteNotification = @"FIRAppDeleteNotification";
  41. NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey";
  42. NSString *const kFIRAppNameKey = @"FIRAppNameKey";
  43. NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey";
  44. NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification";
  45. NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
  46. NSString *const kFIRAppDiagnosticsErrorKey = @"Error";
  47. NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRApp";
  48. NSString *const kFIRAppDiagnosticsSDKNameKey = @"SDKName";
  49. NSString *const kFIRAppDiagnosticsSDKVersionKey = @"SDKVersion";
  50. // Auth internal notification notification and key.
  51. NSString *const FIRAuthStateDidChangeInternalNotification =
  52. @"FIRAuthStateDidChangeInternalNotification";
  53. NSString *const FIRAuthStateDidChangeInternalNotificationAppKey =
  54. @"FIRAuthStateDidChangeInternalNotificationAppKey";
  55. NSString *const FIRAuthStateDidChangeInternalNotificationTokenKey =
  56. @"FIRAuthStateDidChangeInternalNotificationTokenKey";
  57. NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey =
  58. @"FIRAuthStateDidChangeInternalNotificationUIDKey";
  59. /**
  60. * The URL to download plist files.
  61. */
  62. static NSString *const kPlistURL = @"https://console.firebase.google.com/";
  63. @interface FIRApp ()
  64. @property(nonatomic) BOOL alreadySentConfigureNotification;
  65. @property(nonatomic) BOOL alreadySentDeleteNotification;
  66. @end
  67. @implementation FIRApp
  68. // This is necessary since our custom getter prevents `_options` from being created.
  69. @synthesize options = _options;
  70. static NSMutableDictionary *sAllApps;
  71. static FIRApp *sDefaultApp;
  72. + (void)configure {
  73. FIROptions *options = [FIROptions defaultOptions];
  74. if (!options) {
  75. [[NSNotificationCenter defaultCenter]
  76. postNotificationName:kFIRAppDiagnosticsNotification
  77. object:nil
  78. userInfo:@{
  79. kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
  80. kFIRAppDiagnosticsErrorKey : [FIRApp errorForMissingOptions]
  81. }];
  82. [NSException raise:kFirebaseCoreErrorDomain
  83. format:
  84. @"`[FIRApp configure];` (`FirebaseApp.configure()` in Swift) could not find "
  85. @"a valid GoogleService-Info.plist in your project. Please download one "
  86. @"from %@.",
  87. kPlistURL];
  88. }
  89. [FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
  90. }
  91. + (void)configureWithOptions:(FIROptions *)options {
  92. if (!options) {
  93. [NSException raise:kFirebaseCoreErrorDomain
  94. format:@"Options is nil. Please pass a valid options."];
  95. }
  96. [FIRApp configureDefaultAppWithOptions:options sendingNotifications:YES];
  97. }
  98. + (void)configureDefaultAppWithOptions:(FIROptions *)options
  99. sendingNotifications:(BOOL)sendNotifications {
  100. if (sDefaultApp) {
  101. // FIRApp sets up FirebaseAnalytics and does plist validation, but does not cause it
  102. // to fire notifications. So, if the default app already exists, but has not sent out
  103. // configuration notifications, then continue re-initializing it.
  104. if (!sendNotifications || sDefaultApp.alreadySentConfigureNotification) {
  105. [NSException raise:kFirebaseCoreErrorDomain
  106. format:@"Default app has already been configured."];
  107. }
  108. }
  109. @synchronized(self) {
  110. FIRLogDebug(kFIRLoggerCore, @"I-COR000001", @"Configuring the default app.");
  111. sDefaultApp = [[FIRApp alloc] initInstanceWithName:kFIRDefaultAppName options:options];
  112. [FIRApp addAppToAppDictionary:sDefaultApp];
  113. if (!sDefaultApp.alreadySentConfigureNotification && sendNotifications) {
  114. [FIRApp sendNotificationsToSDKs:sDefaultApp];
  115. sDefaultApp.alreadySentConfigureNotification = YES;
  116. }
  117. if (![FIRAppEnvironmentUtil isFromAppStore]) {
  118. // Support for iOS 7 has been deprecated, but will continue to function for the time being.
  119. // Log a notice for developers who are still targeting iOS 7 as the minimum OS version
  120. // supported.
  121. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  122. NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];
  123. NSString *minVersion = info[@"MinimumOSVersion"];
  124. if ([minVersion hasPrefix:@"7."]) {
  125. FIRLogNotice(kFIRLoggerCore, @"I-COR000026",
  126. @"Support for iOS 7 is deprecated and will "
  127. @"stop working in the future. Please upgrade your app to target iOS 8 or "
  128. @"above.");
  129. }
  130. });
  131. }
  132. }
  133. }
  134. + (void)configureWithName:(NSString *)name options:(FIROptions *)options {
  135. if (!name || !options) {
  136. [NSException raise:kFirebaseCoreErrorDomain format:@"Neither name nor options can be nil."];
  137. }
  138. if (name.length == 0) {
  139. [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be empty."];
  140. }
  141. if ([name isEqualToString:kFIRDefaultAppName]) {
  142. [NSException raise:kFirebaseCoreErrorDomain format:@"Name cannot be __FIRAPP_DEFAULT."];
  143. }
  144. for (NSInteger charIndex = 0; charIndex < name.length; charIndex++) {
  145. char character = [name characterAtIndex:charIndex];
  146. if (!((character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') ||
  147. (character >= '0' && character <= '9') || character == '_' || character == '-')) {
  148. [NSException raise:kFirebaseCoreErrorDomain
  149. format:
  150. @"App name should only contain Letters, "
  151. @"Numbers, Underscores, and Dashes."];
  152. }
  153. }
  154. if (sAllApps && sAllApps[name]) {
  155. [NSException raise:kFirebaseCoreErrorDomain
  156. format:@"App named %@ has already been configured.", name];
  157. }
  158. @synchronized(self) {
  159. FIRLogDebug(kFIRLoggerCore, @"I-COR000002", @"Configuring app named %@", name);
  160. FIRApp *app = [[FIRApp alloc] initInstanceWithName:name options:options];
  161. [FIRApp addAppToAppDictionary:app];
  162. if (!app.alreadySentConfigureNotification) {
  163. [FIRApp sendNotificationsToSDKs:app];
  164. app.alreadySentConfigureNotification = YES;
  165. }
  166. }
  167. }
  168. + (FIRApp *)defaultApp {
  169. if (sDefaultApp) {
  170. return sDefaultApp;
  171. }
  172. FIRLogError(kFIRLoggerCore, @"I-COR000003",
  173. @"The default Firebase app has not yet been "
  174. @"configured. Add `[FIRApp configure];` (`FirebaseApp.configure()` in Swift) to your "
  175. @"application initialization. Read more: https://goo.gl/ctyzm8.");
  176. return nil;
  177. }
  178. + (FIRApp *)appNamed:(NSString *)name {
  179. @synchronized(self) {
  180. if (sAllApps) {
  181. FIRApp *app = sAllApps[name];
  182. if (app) {
  183. return app;
  184. }
  185. }
  186. FIRLogError(kFIRLoggerCore, @"I-COR000004", @"App with name %@ does not exist.", name);
  187. return nil;
  188. }
  189. }
  190. + (NSDictionary *)allApps {
  191. @synchronized(self) {
  192. if (!sAllApps) {
  193. FIRLogError(kFIRLoggerCore, @"I-COR000005", @"No app has been configured yet.");
  194. }
  195. NSDictionary *dict = [NSDictionary dictionaryWithDictionary:sAllApps];
  196. return dict;
  197. }
  198. }
  199. // Public only for tests
  200. + (void)resetApps {
  201. sDefaultApp = nil;
  202. [sAllApps removeAllObjects];
  203. sAllApps = nil;
  204. }
  205. - (void)deleteApp:(FIRAppVoidBoolCallback)completion {
  206. @synchronized([self class]) {
  207. if (sAllApps && sAllApps[self.name]) {
  208. FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
  209. [sAllApps removeObjectForKey:self.name];
  210. if ([self.name isEqualToString:kFIRDefaultAppName]) {
  211. sDefaultApp = nil;
  212. }
  213. if (!self.alreadySentDeleteNotification) {
  214. NSDictionary *appInfoDict = @{kFIRAppNameKey : self.name};
  215. [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDeleteNotification
  216. object:[self class]
  217. userInfo:appInfoDict];
  218. self.alreadySentDeleteNotification = YES;
  219. }
  220. completion(YES);
  221. } else {
  222. FIRLogError(kFIRLoggerCore, @"I-COR000007", @"App does not exist.");
  223. completion(NO);
  224. }
  225. }
  226. }
  227. + (void)addAppToAppDictionary:(FIRApp *)app {
  228. if (!sAllApps) {
  229. sAllApps = [NSMutableDictionary dictionary];
  230. }
  231. if ([app configureCore]) {
  232. sAllApps[app.name] = app;
  233. [[NSNotificationCenter defaultCenter]
  234. postNotificationName:kFIRAppDiagnosticsNotification
  235. object:nil
  236. userInfo:@{
  237. kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
  238. kFIRAppDiagnosticsFIRAppKey : app
  239. }];
  240. } else {
  241. [NSException raise:kFirebaseCoreErrorDomain
  242. format:
  243. @"Configuration fails. It may be caused by an invalid GOOGLE_APP_ID in "
  244. @"GoogleService-Info.plist or set in the customized options."];
  245. }
  246. }
  247. - (instancetype)initInstanceWithName:(NSString *)name options:(FIROptions *)options {
  248. self = [super init];
  249. if (self) {
  250. _name = [name copy];
  251. _options = [options copy];
  252. _options.editingLocked = YES;
  253. FIRApp *app = sAllApps[name];
  254. _alreadySentConfigureNotification = app.alreadySentConfigureNotification;
  255. _alreadySentDeleteNotification = app.alreadySentDeleteNotification;
  256. }
  257. return self;
  258. }
  259. - (void)getTokenForcingRefresh:(BOOL)forceRefresh withCallback:(FIRTokenCallback)callback {
  260. if (!_getTokenImplementation) {
  261. callback(nil, nil);
  262. return;
  263. }
  264. _getTokenImplementation(forceRefresh, callback);
  265. }
  266. - (BOOL)configureCore {
  267. [self checkExpectedBundleID];
  268. if (![self isAppIDValid]) {
  269. if (_options.usingOptionsFromDefaultPlist) {
  270. [[NSNotificationCenter defaultCenter]
  271. postNotificationName:kFIRAppDiagnosticsNotification
  272. object:nil
  273. userInfo:@{
  274. kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeCore),
  275. kFIRAppDiagnosticsErrorKey : [FIRApp errorForInvalidAppID],
  276. }];
  277. }
  278. return NO;
  279. }
  280. if (NSClassFromString(@"FIRAppIndexing") != nil) {
  281. FIRLogDebug(kFIRLoggerCore, @"I-COR000024",
  282. @"Firebase App Indexing on iOS is deprecated. "
  283. @"You don't need to take any action at this time. Learn more about Firebase App "
  284. @"Indexing at https://firebase.google.com/docs/app-indexing/.");
  285. }
  286. // Initialize the Analytics once there is a valid options under default app. Analytics should
  287. // always initialize first by itself before the other SDKs.
  288. if ([self.name isEqualToString:kFIRDefaultAppName]) {
  289. Class firAnalyticsClass = NSClassFromString(@"FIRAnalytics");
  290. if (!firAnalyticsClass) {
  291. FIRLogError(kFIRLoggerCore, @"I-COR000022", @"Firebase Analytics is not available.");
  292. } else {
  293. #pragma clang diagnostic push
  294. #pragma clang diagnostic ignored "-Wundeclared-selector"
  295. SEL startWithConfigurationSelector = @selector(startWithConfiguration:options:);
  296. #pragma clang diagnostic pop
  297. if ([firAnalyticsClass respondsToSelector:startWithConfigurationSelector]) {
  298. #pragma clang diagnostic push
  299. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  300. [firAnalyticsClass performSelector:startWithConfigurationSelector
  301. withObject:[FIRConfiguration sharedInstance].analyticsConfiguration
  302. withObject:_options];
  303. #pragma clang diagnostic pop
  304. }
  305. }
  306. }
  307. return YES;
  308. }
  309. - (FIROptions *)options {
  310. return [_options copy];
  311. }
  312. #pragma mark - private
  313. + (void)sendNotificationsToSDKs:(FIRApp *)app {
  314. NSNumber *isDefaultApp = [NSNumber numberWithBool:(app == sDefaultApp)];
  315. NSDictionary *appInfoDict = @{
  316. kFIRAppNameKey : app.name,
  317. kFIRAppIsDefaultAppKey : isDefaultApp,
  318. kFIRGoogleAppIDKey : app.options.googleAppID
  319. };
  320. [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification
  321. object:self
  322. userInfo:appInfoDict];
  323. }
  324. + (NSError *)errorForMissingOptions {
  325. NSDictionary *errorDict = @{
  326. NSLocalizedDescriptionKey :
  327. @"Unable to parse GoogleService-Info.plist in order to configure services.",
  328. NSLocalizedRecoverySuggestionErrorKey :
  329. @"Check formatting and location of GoogleService-Info.plist."
  330. };
  331. return FIRCreateError(kFirebaseCoreErrorDomain, FIRErrorCodeInvalidPlistFile, errorDict);
  332. }
  333. + (NSError *)errorForSubspecConfigurationFailureWithDomain:(NSString *)domain
  334. errorCode:(FIRErrorCode)code
  335. service:(NSString *)service
  336. reason:(NSString *)reason {
  337. NSString *description =
  338. [NSString stringWithFormat:@"Configuration failed for service %@.", service];
  339. NSDictionary *errorDict =
  340. @{NSLocalizedDescriptionKey : description, NSLocalizedFailureReasonErrorKey : reason};
  341. return FIRCreateError(domain, code, errorDict);
  342. }
  343. + (NSError *)errorForInvalidAppID {
  344. NSDictionary *errorDict = @{
  345. NSLocalizedDescriptionKey : @"Unable to validate Google App ID",
  346. NSLocalizedRecoverySuggestionErrorKey :
  347. @"Check formatting and location of GoogleService-Info.plist or GoogleAppID set in the "
  348. @"customized options."
  349. };
  350. return FIRCreateError(kFirebaseCoreErrorDomain, FIRErrorCodeInvalidAppID, errorDict);
  351. }
  352. + (BOOL)isDefaultAppConfigured {
  353. return (sDefaultApp != nil);
  354. }
  355. - (void)checkExpectedBundleID {
  356. NSArray *bundles = [FIRBundleUtil relevantBundles];
  357. NSString *expectedBundleID = [self expectedBundleID];
  358. // The checking is only done when the bundle ID is provided in the serviceInfo dictionary for
  359. // backward compatibility.
  360. if (expectedBundleID != nil &&
  361. ![FIRBundleUtil hasBundleIdentifier:expectedBundleID inBundles:bundles]) {
  362. FIRLogError(kFIRLoggerCore, @"I-COR000008",
  363. @"The project's Bundle ID is inconsistent with "
  364. @"either the Bundle ID in '%@.%@', or the Bundle ID in the options if you are "
  365. @"using a customized options. To ensure that everything can be configured "
  366. @"correctly, you may need to make the Bundle IDs consistent. To continue with this "
  367. @"plist file, you may change your app's bundle identifier to '%@'. Or you can "
  368. @"download a new configuration file that matches your bundle identifier from %@ "
  369. @"and replace the current one.",
  370. kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
  371. }
  372. }
  373. - (nullable NSString *)getUID {
  374. if (!_getUIDImplementation) {
  375. FIRLogWarning(kFIRLoggerCore, @"I-COR000025", @"FIRAuth getUID implementation wasn't set.");
  376. return nil;
  377. }
  378. return _getUIDImplementation();
  379. }
  380. #pragma mark - private - App ID Validation
  381. /**
  382. * Validates the format and fingerprint of the app ID contained in GOOGLE_APP_ID in the plist file.
  383. * This is the main method for validating app ID.
  384. *
  385. * @return YES if the app ID fulfills the expected format and fingerprint, NO otherwise.
  386. */
  387. - (BOOL)isAppIDValid {
  388. NSString *appID = _options.googleAppID;
  389. BOOL isValid = [FIRApp validateAppID:appID];
  390. if (!isValid) {
  391. NSString *expectedBundleID = [self expectedBundleID];
  392. FIRLogError(kFIRLoggerCore, @"I-COR000009",
  393. @"The GOOGLE_APP_ID either in the plist file "
  394. @"'%@.%@' or the one set in the customized options is invalid. If you are using "
  395. @"the plist file, use the iOS version of bundle identifier to download the file, "
  396. @"and do not manually edit the GOOGLE_APP_ID. You may change your app's bundle "
  397. @"identifier to '%@'. Or you can download a new configuration file that matches "
  398. @"your bundle identifier from %@ and replace the current one.",
  399. kServiceInfoFileName, kServiceInfoFileType, expectedBundleID, kPlistURL);
  400. };
  401. return isValid;
  402. }
  403. + (BOOL)validateAppID:(NSString *)appID {
  404. // Failing validation only occurs when we are sure we are looking at a V2 app ID and it does not
  405. // have a valid fingerprint, otherwise we just warn about the potential issue.
  406. if (!appID.length) {
  407. return NO;
  408. }
  409. // All app IDs must start with at least "<version number>:".
  410. NSString *const versionPattern = @"^\\d+:";
  411. NSRegularExpression *versionRegex =
  412. [NSRegularExpression regularExpressionWithPattern:versionPattern options:0 error:NULL];
  413. if (!versionRegex) {
  414. return NO;
  415. }
  416. NSRange appIDRange = NSMakeRange(0, appID.length);
  417. NSArray *versionMatches = [versionRegex matchesInString:appID options:0 range:appIDRange];
  418. if (versionMatches.count != 1) {
  419. return NO;
  420. }
  421. NSRange versionRange = [(NSTextCheckingResult *)versionMatches.firstObject range];
  422. NSString *appIDVersion = [appID substringWithRange:versionRange];
  423. NSArray *knownVersions = @[ @"1:" ];
  424. if (![knownVersions containsObject:appIDVersion]) {
  425. // Permit unknown yet properly formatted app ID versions.
  426. return YES;
  427. }
  428. if (![FIRApp validateAppIDFormat:appID withVersion:appIDVersion]) {
  429. return NO;
  430. }
  431. if (![FIRApp validateAppIDFingerprint:appID withVersion:appIDVersion]) {
  432. return NO;
  433. }
  434. return YES;
  435. }
  436. + (NSString *)actualBundleID {
  437. return [[NSBundle mainBundle] bundleIdentifier];
  438. }
  439. /**
  440. * Validates that the format of the app ID string is what is expected based on the supplied version.
  441. * The version must end in ":".
  442. *
  443. * For v1 app ids the format is expected to be
  444. * '<version #>:<project number>:ios:<fingerprint of bundle id>'.
  445. *
  446. * This method does not verify that the contents of the app id are correct, just that they fulfill
  447. * the expected format.
  448. *
  449. * @param appID Contents of GOOGLE_APP_ID from the plist file.
  450. * @param version Indicates what version of the app id format this string should be.
  451. * @return YES if provided string fufills the expected format, NO otherwise.
  452. */
  453. + (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version {
  454. if (!appID.length || !version.length) {
  455. return NO;
  456. }
  457. if (![version hasSuffix:@":"]) {
  458. return NO;
  459. }
  460. if (![appID hasPrefix:version]) {
  461. return NO;
  462. }
  463. NSString *const pattern = @"^\\d+:ios:[a-f0-9]+$";
  464. NSRegularExpression *regex =
  465. [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
  466. if (!regex) {
  467. return NO;
  468. }
  469. NSRange localRange = NSMakeRange(version.length, appID.length - version.length);
  470. NSUInteger numberOfMatches = [regex numberOfMatchesInString:appID options:0 range:localRange];
  471. if (numberOfMatches != 1) {
  472. return NO;
  473. }
  474. return YES;
  475. }
  476. /**
  477. * Validates that the fingerprint of the app ID string is what is expected based on the supplied
  478. * version. The version must end in ":".
  479. *
  480. * Note that the v1 hash algorithm is not permitted on the client and cannot be fully validated.
  481. *
  482. * @param appID Contents of GOOGLE_APP_ID from the plist file.
  483. * @param version Indicates what version of the app id format this string should be.
  484. * @return YES if provided string fufills the expected fingerprint and the version is known, NO
  485. * otherwise.
  486. */
  487. + (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version {
  488. if (!appID.length || !version.length) {
  489. return NO;
  490. }
  491. if (![version hasSuffix:@":"]) {
  492. return NO;
  493. }
  494. if (![appID hasPrefix:version]) {
  495. return NO;
  496. }
  497. // Extract the supplied fingerprint from the supplied app ID.
  498. // This assumes the app ID format is the same for all known versions below. If the app ID format
  499. // changes in future versions, the tokenizing of the app ID format will need to take into account
  500. // the version of the app ID.
  501. NSArray *components = [appID componentsSeparatedByString:@":"];
  502. if (components.count != 4) {
  503. return NO;
  504. }
  505. NSString *suppliedFingerprintString = components[3];
  506. if (!suppliedFingerprintString.length) {
  507. return NO;
  508. }
  509. uint64_t suppliedFingerprint;
  510. NSScanner *scanner = [NSScanner scannerWithString:suppliedFingerprintString];
  511. if (![scanner scanHexLongLong:&suppliedFingerprint]) {
  512. return NO;
  513. }
  514. if ([version isEqual:@"1:"]) {
  515. // The v1 hash algorithm is not permitted on the client so the actual hash cannot be validated.
  516. return YES;
  517. }
  518. // Unknown version.
  519. return NO;
  520. }
  521. - (NSString *)expectedBundleID {
  522. return _options.bundleID;
  523. }
  524. // end App ID validation
  525. #pragma mark
  526. - (void)sendLogsWithServiceName:(NSString *)serviceName
  527. version:(NSString *)version
  528. error:(NSError *)error {
  529. NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithDictionary:@{
  530. kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeSDK),
  531. kFIRAppDiagnosticsSDKNameKey : serviceName,
  532. kFIRAppDiagnosticsSDKVersionKey : version,
  533. kFIRAppDiagnosticsFIRAppKey : self
  534. }];
  535. if (error) {
  536. userInfo[kFIRAppDiagnosticsErrorKey] = error;
  537. }
  538. [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppDiagnosticsNotification
  539. object:nil
  540. userInfo:userInfo];
  541. }
  542. @end