RCTUtils.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTUtils.h"
  8. #import <dlfcn.h>
  9. #import <mach/mach_time.h>
  10. #import <objc/message.h>
  11. #import <objc/runtime.h>
  12. #import <zlib.h>
  13. #import <UIKit/UIKit.h>
  14. #import <CommonCrypto/CommonCrypto.h>
  15. #import <React/RCTUtilsUIOverride.h>
  16. #import "RCTAssert.h"
  17. #import "RCTLog.h"
  18. NSString *const RCTErrorUnspecified = @"EUNSPECIFIED";
  19. // Returns the Path of Home directory
  20. NSString *__nullable RCTHomePath(void);
  21. // Returns the relative path within the Home for an absolute URL
  22. // (or nil, if the URL does not specify a path within the Home directory)
  23. NSString *__nullable RCTHomePathForURL(NSURL *__nullable URL);
  24. // Determines if a given image URL refers to a image in Home directory (~)
  25. BOOL RCTIsHomeAssetURL(NSURL *__nullable imageURL);
  26. static NSString *__nullable _RCTJSONStringifyNoRetry(id __nullable jsonObject, NSError **error)
  27. {
  28. if (!jsonObject) {
  29. return nil;
  30. }
  31. static SEL JSONKitSelector = NULL;
  32. static NSSet<Class> *collectionTypes;
  33. static dispatch_once_t onceToken;
  34. dispatch_once(&onceToken, ^{
  35. SEL selector = NSSelectorFromString(@"JSONStringWithOptions:error:");
  36. if ([NSDictionary instancesRespondToSelector:selector]) {
  37. JSONKitSelector = selector;
  38. collectionTypes = [NSSet setWithObjects:[NSArray class],
  39. [NSMutableArray class],
  40. [NSDictionary class],
  41. [NSMutableDictionary class],
  42. nil];
  43. }
  44. });
  45. @try {
  46. // Use JSONKit if available and object is not a fragment
  47. if (JSONKitSelector && [collectionTypes containsObject:[jsonObject classForCoder]]) {
  48. return ((NSString * (*)(id, SEL, int, NSError **)) objc_msgSend)(jsonObject, JSONKitSelector, 0, error);
  49. }
  50. // Use Foundation JSON method
  51. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject
  52. options:(NSJSONWritingOptions)NSJSONReadingAllowFragments
  53. error:error];
  54. return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
  55. } @catch (NSException *exception) {
  56. // Convert exception to error
  57. if (error) {
  58. *error = [NSError errorWithDomain:RCTErrorDomain
  59. code:0
  60. userInfo:@{NSLocalizedDescriptionKey : exception.description ?: @""}];
  61. }
  62. return nil;
  63. }
  64. }
  65. NSString *__nullable RCTJSONStringify(id __nullable jsonObject, NSError **error)
  66. {
  67. if (error) {
  68. return _RCTJSONStringifyNoRetry(jsonObject, error);
  69. } else {
  70. NSError *localError;
  71. NSString *json = _RCTJSONStringifyNoRetry(jsonObject, &localError);
  72. if (localError) {
  73. RCTLogError(@"RCTJSONStringify() encountered the following error: %@", localError.localizedDescription);
  74. // Sanitize the data, then retry. This is slow, but it prevents uncaught
  75. // data issues from crashing in production
  76. return _RCTJSONStringifyNoRetry(RCTJSONClean(jsonObject), NULL);
  77. }
  78. return json;
  79. }
  80. }
  81. static id __nullable _RCTJSONParse(NSString *__nullable jsonString, BOOL mutable, NSError **error)
  82. {
  83. static SEL JSONKitSelector = NULL;
  84. static SEL JSONKitMutableSelector = NULL;
  85. static dispatch_once_t onceToken;
  86. dispatch_once(&onceToken, ^{
  87. SEL selector = NSSelectorFromString(@"objectFromJSONStringWithParseOptions:error:");
  88. if ([NSString instancesRespondToSelector:selector]) {
  89. JSONKitSelector = selector;
  90. JSONKitMutableSelector = NSSelectorFromString(@"mutableObjectFromJSONStringWithParseOptions:error:");
  91. }
  92. });
  93. if (jsonString) {
  94. // Use JSONKit if available and string is not a fragment
  95. if (JSONKitSelector) {
  96. NSInteger length = jsonString.length;
  97. for (NSInteger i = 0; i < length; i++) {
  98. unichar c = [jsonString characterAtIndex:i];
  99. if (strchr("{[", c)) {
  100. static const int options = (1 << 2); // loose unicode
  101. SEL selector = mutable ? JSONKitMutableSelector : JSONKitSelector;
  102. return ((id(*)(id, SEL, int, NSError **))objc_msgSend)(jsonString, selector, options, error);
  103. }
  104. if (!strchr(" \r\n\t", c)) {
  105. break;
  106. }
  107. }
  108. }
  109. // Use Foundation JSON method
  110. NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
  111. if (!jsonData) {
  112. jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
  113. if (jsonData) {
  114. RCTLogWarn(
  115. @"RCTJSONParse received the following string, which could "
  116. "not be losslessly converted to UTF8 data: '%@'",
  117. jsonString);
  118. } else {
  119. NSString *errorMessage = @"RCTJSONParse received invalid UTF8 data";
  120. if (error) {
  121. *error = RCTErrorWithMessage(errorMessage);
  122. } else {
  123. RCTLogError(@"%@", errorMessage);
  124. }
  125. return nil;
  126. }
  127. }
  128. NSJSONReadingOptions options = NSJSONReadingAllowFragments;
  129. if (mutable) {
  130. options |= NSJSONReadingMutableContainers;
  131. }
  132. return [NSJSONSerialization JSONObjectWithData:jsonData options:options error:error];
  133. }
  134. return nil;
  135. }
  136. id __nullable RCTJSONParse(NSString *__nullable jsonString, NSError **error)
  137. {
  138. return _RCTJSONParse(jsonString, NO, error);
  139. }
  140. id __nullable RCTJSONParseMutable(NSString *__nullable jsonString, NSError **error)
  141. {
  142. return _RCTJSONParse(jsonString, YES, error);
  143. }
  144. id RCTJSONClean(id object)
  145. {
  146. static dispatch_once_t onceToken;
  147. static NSSet<Class> *validLeafTypes;
  148. dispatch_once(&onceToken, ^{
  149. validLeafTypes =
  150. [[NSSet alloc] initWithArray:@ [[NSString class], [NSMutableString class], [NSNumber class], [NSNull class], ]];
  151. });
  152. if ([validLeafTypes containsObject:[object classForCoder]]) {
  153. if ([object isKindOfClass:[NSNumber class]]) {
  154. return @(RCTZeroIfNaN([object doubleValue]));
  155. }
  156. if ([object isKindOfClass:[NSString class]]) {
  157. if ([object UTF8String] == NULL) {
  158. return (id)kCFNull;
  159. }
  160. }
  161. return object;
  162. }
  163. if ([object isKindOfClass:[NSDictionary class]]) {
  164. __block BOOL copy = NO;
  165. NSMutableDictionary<NSString *, id> *values = [[NSMutableDictionary alloc] initWithCapacity:[object count]];
  166. [object enumerateKeysAndObjectsUsingBlock:^(NSString *key, id item, __unused BOOL *stop) {
  167. id value = RCTJSONClean(item);
  168. values[key] = value;
  169. copy |= value != item;
  170. }];
  171. return copy ? values : object;
  172. }
  173. if ([object isKindOfClass:[NSArray class]]) {
  174. __block BOOL copy = NO;
  175. __block NSArray *values = object;
  176. [object enumerateObjectsUsingBlock:^(id item, NSUInteger idx, __unused BOOL *stop) {
  177. id value = RCTJSONClean(item);
  178. if (copy) {
  179. [(NSMutableArray *)values addObject:value];
  180. } else if (value != item) {
  181. // Converted value is different, so we'll need to copy the array
  182. values = [[NSMutableArray alloc] initWithCapacity:values.count];
  183. for (NSUInteger i = 0; i < idx; i++) {
  184. [(NSMutableArray *)values addObject:object[i]];
  185. }
  186. [(NSMutableArray *)values addObject:value];
  187. copy = YES;
  188. }
  189. }];
  190. return values;
  191. }
  192. return (id)kCFNull;
  193. }
  194. NSString *RCTMD5Hash(NSString *string)
  195. {
  196. const char *str = string.UTF8String;
  197. unsigned char result[CC_MD5_DIGEST_LENGTH];
  198. CC_MD5(str, (CC_LONG)strlen(str), result);
  199. return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
  200. result[0],
  201. result[1],
  202. result[2],
  203. result[3],
  204. result[4],
  205. result[5],
  206. result[6],
  207. result[7],
  208. result[8],
  209. result[9],
  210. result[10],
  211. result[11],
  212. result[12],
  213. result[13],
  214. result[14],
  215. result[15]];
  216. }
  217. BOOL RCTIsMainQueue()
  218. {
  219. static void *mainQueueKey = &mainQueueKey;
  220. static dispatch_once_t onceToken;
  221. dispatch_once(&onceToken, ^{
  222. dispatch_queue_set_specific(dispatch_get_main_queue(), mainQueueKey, mainQueueKey, NULL);
  223. });
  224. return dispatch_get_specific(mainQueueKey) == mainQueueKey;
  225. }
  226. void RCTExecuteOnMainQueue(dispatch_block_t block)
  227. {
  228. if (RCTIsMainQueue()) {
  229. block();
  230. } else {
  231. dispatch_async(dispatch_get_main_queue(), ^{
  232. block();
  233. });
  234. }
  235. }
  236. // Please do not use this method
  237. // unless you know what you are doing.
  238. void RCTUnsafeExecuteOnMainQueueSync(dispatch_block_t block)
  239. {
  240. if (RCTIsMainQueue()) {
  241. block();
  242. } else {
  243. dispatch_sync(dispatch_get_main_queue(), ^{
  244. block();
  245. });
  246. }
  247. }
  248. static void RCTUnsafeExecuteOnMainQueueOnceSync(dispatch_once_t *onceToken, dispatch_block_t block)
  249. {
  250. // The solution was borrowed from a post by Ben Alpert:
  251. // https://benalpert.com/2014/04/02/dispatch-once-initialization-on-the-main-thread.html
  252. // See also: https://www.mikeash.com/pyblog/friday-qa-2014-06-06-secrets-of-dispatch_once.html
  253. if (RCTIsMainQueue()) {
  254. dispatch_once(onceToken, block);
  255. } else {
  256. if (DISPATCH_EXPECT(*onceToken == 0L, NO)) {
  257. dispatch_sync(dispatch_get_main_queue(), ^{
  258. dispatch_once(onceToken, block);
  259. });
  260. }
  261. }
  262. }
  263. CGFloat RCTScreenScale()
  264. {
  265. static dispatch_once_t onceToken;
  266. static CGFloat scale;
  267. RCTUnsafeExecuteOnMainQueueOnceSync(&onceToken, ^{
  268. scale = [UIScreen mainScreen].scale;
  269. });
  270. return scale;
  271. }
  272. CGSize RCTScreenSize()
  273. {
  274. // FIXME: this caches the bounds at app start, whatever those were, and then
  275. // doesn't update when the device is rotated. We need to find another thread-
  276. // safe way to get the screen size.
  277. static CGSize size;
  278. static dispatch_once_t onceToken;
  279. dispatch_once(&onceToken, ^{
  280. RCTUnsafeExecuteOnMainQueueSync(^{
  281. size = [UIScreen mainScreen].bounds.size;
  282. });
  283. });
  284. return size;
  285. }
  286. CGFloat RCTRoundPixelValue(CGFloat value)
  287. {
  288. CGFloat scale = RCTScreenScale();
  289. return round(value * scale) / scale;
  290. }
  291. CGFloat RCTCeilPixelValue(CGFloat value)
  292. {
  293. CGFloat scale = RCTScreenScale();
  294. return ceil(value * scale) / scale;
  295. }
  296. CGFloat RCTFloorPixelValue(CGFloat value)
  297. {
  298. CGFloat scale = RCTScreenScale();
  299. return floor(value * scale) / scale;
  300. }
  301. CGSize RCTSizeInPixels(CGSize pointSize, CGFloat scale)
  302. {
  303. return (CGSize){
  304. ceil(pointSize.width * scale),
  305. ceil(pointSize.height * scale),
  306. };
  307. }
  308. void RCTSwapClassMethods(Class cls, SEL original, SEL replacement)
  309. {
  310. Method originalMethod = class_getClassMethod(cls, original);
  311. IMP originalImplementation = method_getImplementation(originalMethod);
  312. const char *originalArgTypes = method_getTypeEncoding(originalMethod);
  313. Method replacementMethod = class_getClassMethod(cls, replacement);
  314. IMP replacementImplementation = method_getImplementation(replacementMethod);
  315. const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
  316. if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes)) {
  317. class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
  318. } else {
  319. method_exchangeImplementations(originalMethod, replacementMethod);
  320. }
  321. }
  322. void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement)
  323. {
  324. Method originalMethod = class_getInstanceMethod(cls, original);
  325. IMP originalImplementation = method_getImplementation(originalMethod);
  326. const char *originalArgTypes = method_getTypeEncoding(originalMethod);
  327. Method replacementMethod = class_getInstanceMethod(cls, replacement);
  328. IMP replacementImplementation = method_getImplementation(replacementMethod);
  329. const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
  330. if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes)) {
  331. class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
  332. } else {
  333. method_exchangeImplementations(originalMethod, replacementMethod);
  334. }
  335. }
  336. BOOL RCTClassOverridesClassMethod(Class cls, SEL selector)
  337. {
  338. return RCTClassOverridesInstanceMethod(object_getClass(cls), selector);
  339. }
  340. BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector)
  341. {
  342. unsigned int numberOfMethods;
  343. Method *methods = class_copyMethodList(cls, &numberOfMethods);
  344. for (unsigned int i = 0; i < numberOfMethods; i++) {
  345. if (method_getName(methods[i]) == selector) {
  346. free(methods);
  347. return YES;
  348. }
  349. }
  350. free(methods);
  351. return NO;
  352. }
  353. NSDictionary<NSString *, id>
  354. *RCTMakeError(NSString *message, id __nullable toStringify, NSDictionary<NSString *, id> *__nullable extraData)
  355. {
  356. if (toStringify) {
  357. message = [message stringByAppendingString:[toStringify description]];
  358. }
  359. NSMutableDictionary<NSString *, id> *error = [extraData mutableCopy] ?: [NSMutableDictionary new];
  360. error[@"message"] = message;
  361. return error;
  362. }
  363. NSDictionary<NSString *, id> *
  364. RCTMakeAndLogError(NSString *message, id __nullable toStringify, NSDictionary<NSString *, id> *__nullable extraData)
  365. {
  366. NSDictionary<NSString *, id> *error = RCTMakeError(message, toStringify, extraData);
  367. RCTLogError(@"\nError: %@", error);
  368. return error;
  369. }
  370. NSDictionary<NSString *, id> *RCTJSErrorFromNSError(NSError *error)
  371. {
  372. NSString *codeWithDomain =
  373. [NSString stringWithFormat:@"E%@%lld", error.domain.uppercaseString, (long long)error.code];
  374. return RCTJSErrorFromCodeMessageAndNSError(codeWithDomain, error.localizedDescription, error);
  375. }
  376. // TODO: Can we just replace RCTMakeError with this function instead?
  377. NSDictionary<NSString *, id>
  378. *RCTJSErrorFromCodeMessageAndNSError(NSString *code, NSString *message, NSError *__nullable error)
  379. {
  380. NSString *errorMessage;
  381. NSArray<NSString *> *stackTrace = [NSThread callStackSymbols];
  382. NSMutableDictionary *userInfo;
  383. NSMutableDictionary<NSString *, id> *errorInfo = [NSMutableDictionary dictionaryWithObject:stackTrace
  384. forKey:@"nativeStackIOS"];
  385. if (error) {
  386. errorMessage = error.localizedDescription ?: @"Unknown error from a native module";
  387. errorInfo[@"domain"] = error.domain ?: RCTErrorDomain;
  388. if (error.userInfo) {
  389. userInfo = [error.userInfo mutableCopy];
  390. if (userInfo != nil && userInfo[NSUnderlyingErrorKey] != nil) {
  391. NSError *underlyingError = error.userInfo[NSUnderlyingErrorKey];
  392. NSString *underlyingCode = [NSString stringWithFormat:@"%d", (int)underlyingError.code];
  393. userInfo[NSUnderlyingErrorKey] =
  394. RCTJSErrorFromCodeMessageAndNSError(underlyingCode, @"underlying error", underlyingError);
  395. }
  396. }
  397. } else {
  398. errorMessage = @"Unknown error from a native module";
  399. errorInfo[@"domain"] = RCTErrorDomain;
  400. userInfo = nil;
  401. }
  402. errorInfo[@"code"] = code ?: RCTErrorUnspecified;
  403. errorInfo[@"userInfo"] = RCTNullIfNil(userInfo);
  404. // Allow for explicit overriding of the error message
  405. errorMessage = message ?: errorMessage;
  406. return RCTMakeError(errorMessage, nil, errorInfo);
  407. }
  408. BOOL RCTRunningInTestEnvironment(void)
  409. {
  410. static BOOL isTestEnvironment = NO;
  411. static dispatch_once_t onceToken;
  412. dispatch_once(&onceToken, ^{
  413. NSDictionary *environment = [[NSProcessInfo processInfo] environment];
  414. isTestEnvironment = objc_lookUpClass("SenTestCase") || objc_lookUpClass("XCTest") ||
  415. objc_lookUpClass("SnapshotTestAppDelegate") || [environment[@"IS_TESTING"] boolValue];
  416. });
  417. return isTestEnvironment;
  418. }
  419. BOOL RCTRunningInAppExtension(void)
  420. {
  421. return [[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"];
  422. }
  423. UIApplication *__nullable RCTSharedApplication(void)
  424. {
  425. if (RCTRunningInAppExtension()) {
  426. return nil;
  427. }
  428. return [[UIApplication class] performSelector:@selector(sharedApplication)];
  429. }
  430. UIWindow *__nullable RCTKeyWindow(void)
  431. {
  432. if (RCTRunningInAppExtension()) {
  433. return nil;
  434. }
  435. // TODO: replace with a more robust solution
  436. for (UIWindow *window in RCTSharedApplication().windows) {
  437. if (window.keyWindow) {
  438. return window;
  439. }
  440. }
  441. return nil;
  442. }
  443. UIViewController *__nullable RCTPresentedViewController(void)
  444. {
  445. if ([RCTUtilsUIOverride hasPresentedViewController]) {
  446. return [RCTUtilsUIOverride presentedViewController];
  447. }
  448. UIViewController *controller = RCTKeyWindow().rootViewController;
  449. UIViewController *presentedController = controller.presentedViewController;
  450. while (presentedController && ![presentedController isBeingDismissed]) {
  451. controller = presentedController;
  452. presentedController = controller.presentedViewController;
  453. }
  454. return controller;
  455. }
  456. BOOL RCTForceTouchAvailable(void)
  457. {
  458. static BOOL forceSupported;
  459. static dispatch_once_t onceToken;
  460. dispatch_once(&onceToken, ^{
  461. forceSupported =
  462. [UITraitCollection class] && [UITraitCollection instancesRespondToSelector:@selector(forceTouchCapability)];
  463. });
  464. return forceSupported &&
  465. (RCTKeyWindow() ?: [UIView new]).traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable;
  466. }
  467. NSError *RCTErrorWithMessage(NSString *message)
  468. {
  469. NSDictionary<NSString *, id> *errorInfo = @{NSLocalizedDescriptionKey : message};
  470. return [[NSError alloc] initWithDomain:RCTErrorDomain code:0 userInfo:errorInfo];
  471. }
  472. double RCTZeroIfNaN(double value)
  473. {
  474. return isnan(value) || isinf(value) ? 0 : value;
  475. }
  476. double RCTSanitizeNaNValue(double value, NSString *property)
  477. {
  478. if (!isnan(value) && !isinf(value)) {
  479. return value;
  480. }
  481. RCTLogWarn(@"The value `%@` equals NaN or INF and will be replaced by `0`.", property);
  482. return 0;
  483. }
  484. NSURL *RCTDataURL(NSString *mimeType, NSData *data)
  485. {
  486. return [NSURL
  487. URLWithString:[NSString stringWithFormat:@"data:%@;base64,%@",
  488. mimeType,
  489. [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]];
  490. }
  491. BOOL RCTIsGzippedData(NSData *__nullable); // exposed for unit testing purposes
  492. BOOL RCTIsGzippedData(NSData *__nullable data)
  493. {
  494. UInt8 *bytes = (UInt8 *)data.bytes;
  495. return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
  496. }
  497. NSData *__nullable RCTGzipData(NSData *__nullable input, float level)
  498. {
  499. if (input.length == 0 || RCTIsGzippedData(input)) {
  500. return input;
  501. }
  502. void *libz = dlopen("/usr/lib/libz.dylib", RTLD_LAZY);
  503. int (*deflateInit2_)(z_streamp, int, int, int, int, int, const char *, int) = dlsym(libz, "deflateInit2_");
  504. int (*deflate)(z_streamp, int) = dlsym(libz, "deflate");
  505. int (*deflateEnd)(z_streamp) = dlsym(libz, "deflateEnd");
  506. z_stream stream;
  507. stream.zalloc = Z_NULL;
  508. stream.zfree = Z_NULL;
  509. stream.opaque = Z_NULL;
  510. stream.avail_in = (uint)input.length;
  511. stream.next_in = (Bytef *)input.bytes;
  512. stream.total_out = 0;
  513. stream.avail_out = 0;
  514. static const NSUInteger RCTGZipChunkSize = 16384;
  515. NSMutableData *output = nil;
  516. int compression = (level < 0.0f) ? Z_DEFAULT_COMPRESSION : (int)(roundf(level * 9));
  517. if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
  518. output = [NSMutableData dataWithLength:RCTGZipChunkSize];
  519. while (stream.avail_out == 0) {
  520. if (stream.total_out >= output.length) {
  521. output.length += RCTGZipChunkSize;
  522. }
  523. stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out;
  524. stream.avail_out = (uInt)(output.length - stream.total_out);
  525. deflate(&stream, Z_FINISH);
  526. }
  527. deflateEnd(&stream);
  528. output.length = stream.total_out;
  529. }
  530. dlclose(libz);
  531. return output;
  532. }
  533. static NSString *RCTRelativePathForURL(NSString *basePath, NSURL *__nullable URL)
  534. {
  535. if (!URL.fileURL) {
  536. // Not a file path
  537. return nil;
  538. }
  539. NSString *path = [NSString stringWithUTF8String:[URL fileSystemRepresentation]];
  540. if (![path hasPrefix:basePath]) {
  541. // Not a bundle-relative file
  542. return nil;
  543. }
  544. path = [path substringFromIndex:basePath.length];
  545. if ([path hasPrefix:@"/"]) {
  546. path = [path substringFromIndex:1];
  547. }
  548. return path;
  549. }
  550. NSString *__nullable RCTLibraryPath(void)
  551. {
  552. static NSString *libraryPath = nil;
  553. static dispatch_once_t onceToken;
  554. dispatch_once(&onceToken, ^{
  555. libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
  556. });
  557. return libraryPath;
  558. }
  559. NSString *__nullable RCTHomePath(void)
  560. {
  561. static NSString *homePath = nil;
  562. static dispatch_once_t onceToken;
  563. dispatch_once(&onceToken, ^{
  564. homePath = NSHomeDirectory();
  565. });
  566. return homePath;
  567. }
  568. NSString *__nullable RCTBundlePathForURL(NSURL *__nullable URL)
  569. {
  570. return RCTRelativePathForURL([[NSBundle mainBundle] resourcePath], URL);
  571. }
  572. NSString *__nullable RCTLibraryPathForURL(NSURL *__nullable URL)
  573. {
  574. return RCTRelativePathForURL(RCTLibraryPath(), URL);
  575. }
  576. NSString *__nullable RCTHomePathForURL(NSURL *__nullable URL)
  577. {
  578. return RCTRelativePathForURL(RCTHomePath(), URL);
  579. }
  580. static BOOL RCTIsImageAssetsPath(NSString *path)
  581. {
  582. NSString *extension = [path pathExtension];
  583. return [extension isEqualToString:@"png"] || [extension isEqualToString:@"jpg"];
  584. }
  585. BOOL RCTIsBundleAssetURL(NSURL *__nullable imageURL)
  586. {
  587. return RCTIsImageAssetsPath(RCTBundlePathForURL(imageURL));
  588. }
  589. BOOL RCTIsLibraryAssetURL(NSURL *__nullable imageURL)
  590. {
  591. return RCTIsImageAssetsPath(RCTLibraryPathForURL(imageURL));
  592. }
  593. BOOL RCTIsHomeAssetURL(NSURL *__nullable imageURL)
  594. {
  595. return RCTIsImageAssetsPath(RCTHomePathForURL(imageURL));
  596. }
  597. BOOL RCTIsLocalAssetURL(NSURL *__nullable imageURL)
  598. {
  599. return RCTIsBundleAssetURL(imageURL) || RCTIsHomeAssetURL(imageURL);
  600. }
  601. static NSString *bundleName(NSBundle *bundle)
  602. {
  603. NSString *name = bundle.infoDictionary[@"CFBundleName"];
  604. if (!name) {
  605. name = [[bundle.bundlePath lastPathComponent] stringByDeletingPathExtension];
  606. }
  607. return name;
  608. }
  609. static NSBundle *bundleForPath(NSString *key)
  610. {
  611. static NSMutableDictionary *bundleCache;
  612. if (!bundleCache) {
  613. bundleCache = [NSMutableDictionary new];
  614. bundleCache[@"main"] = [NSBundle mainBundle];
  615. // Initialize every bundle in the array
  616. for (NSString *path in [[NSBundle mainBundle] pathsForResourcesOfType:@"bundle" inDirectory:nil]) {
  617. [NSBundle bundleWithPath:path];
  618. }
  619. // The bundles initialized above will now also be in `allBundles`
  620. for (NSBundle *bundle in [NSBundle allBundles]) {
  621. bundleCache[bundleName(bundle)] = bundle;
  622. }
  623. }
  624. return bundleCache[key];
  625. }
  626. UIImage *__nullable RCTImageFromLocalBundleAssetURL(NSURL *imageURL)
  627. {
  628. if (![imageURL.scheme isEqualToString:@"file"]) {
  629. // We only want to check for local file assets
  630. return nil;
  631. }
  632. // Get the bundle URL, and add the image URL
  633. // Note that we have to add both host and path, since host is the first "assets" part
  634. // while path is the rest of the URL
  635. NSURL *bundleImageUrl = [[[NSBundle mainBundle] bundleURL]
  636. URLByAppendingPathComponent:[imageURL.host stringByAppendingString:imageURL.path]];
  637. return RCTImageFromLocalAssetURL(bundleImageUrl);
  638. }
  639. UIImage *__nullable RCTImageFromLocalAssetURL(NSURL *imageURL)
  640. {
  641. NSString *imageName = RCTBundlePathForURL(imageURL);
  642. NSBundle *bundle = nil;
  643. NSArray *imagePathComponents = [imageName pathComponents];
  644. if ([imagePathComponents count] > 1 &&
  645. [[[imagePathComponents firstObject] pathExtension] isEqualToString:@"bundle"]) {
  646. NSString *bundlePath = [imagePathComponents firstObject];
  647. bundle = bundleForPath([bundlePath stringByDeletingPathExtension]);
  648. imageName = [imageName substringFromIndex:(bundlePath.length + 1)];
  649. }
  650. UIImage *image = nil;
  651. if (imageName) {
  652. if (bundle) {
  653. image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];
  654. } else {
  655. image = [UIImage imageNamed:imageName];
  656. }
  657. }
  658. if (!image) {
  659. // Attempt to load from the file system
  660. const char *fileSystemCString = [imageURL fileSystemRepresentation];
  661. if (fileSystemCString != NULL) {
  662. NSString *filePath = [NSString stringWithUTF8String:fileSystemCString];
  663. if (filePath.pathExtension.length == 0) {
  664. filePath = [filePath stringByAppendingPathExtension:@"png"];
  665. }
  666. image = [UIImage imageWithContentsOfFile:filePath];
  667. }
  668. }
  669. if (!image && !bundle) {
  670. // We did not find the image in the mainBundle, check in other shipped frameworks.
  671. NSArray<NSURL *> *possibleFrameworks =
  672. [[NSFileManager defaultManager] contentsOfDirectoryAtURL:[[NSBundle mainBundle] privateFrameworksURL]
  673. includingPropertiesForKeys:@[]
  674. options:0
  675. error:nil];
  676. for (NSURL *frameworkURL in possibleFrameworks) {
  677. bundle = [NSBundle bundleWithURL:frameworkURL];
  678. image = [UIImage imageNamed:imageName inBundle:bundle compatibleWithTraitCollection:nil];
  679. if (image) {
  680. RCTLogWarn(@"Image %@ not found in mainBundle, but found in %@", imageName, bundle);
  681. break;
  682. }
  683. }
  684. }
  685. return image;
  686. }
  687. RCT_EXTERN NSString *__nullable RCTTempFilePath(NSString *extension, NSError **error)
  688. {
  689. static NSError *setupError = nil;
  690. static NSString *directory;
  691. static dispatch_once_t onceToken;
  692. dispatch_once(&onceToken, ^{
  693. directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"ReactNative"];
  694. // If the temporary directory already exists, we'll delete it to ensure
  695. // that temp files from the previous run have all been deleted. This is not
  696. // a security measure, it simply prevents the temp directory from using too
  697. // much space, as the circumstances under which iOS clears it automatically
  698. // are not well-defined.
  699. NSFileManager *fileManager = [NSFileManager new];
  700. if ([fileManager fileExistsAtPath:directory]) {
  701. [fileManager removeItemAtPath:directory error:NULL];
  702. }
  703. if (![fileManager fileExistsAtPath:directory]) {
  704. NSError *localError = nil;
  705. if (![fileManager createDirectoryAtPath:directory
  706. withIntermediateDirectories:YES
  707. attributes:nil
  708. error:&localError]) {
  709. // This is bad
  710. RCTLogError(@"Failed to create temporary directory: %@", localError);
  711. setupError = localError;
  712. directory = nil;
  713. }
  714. }
  715. });
  716. if (!directory || setupError) {
  717. if (error) {
  718. *error = setupError;
  719. }
  720. return nil;
  721. }
  722. // Append a unique filename
  723. NSString *filename = [NSUUID new].UUIDString;
  724. if (extension) {
  725. filename = [filename stringByAppendingPathExtension:extension];
  726. }
  727. return [directory stringByAppendingPathComponent:filename];
  728. }
  729. RCT_EXTERN void RCTGetRGBAColorComponents(CGColorRef color, CGFloat rgba[4])
  730. {
  731. CGColorSpaceModel model = CGColorSpaceGetModel(CGColorGetColorSpace(color));
  732. const CGFloat *components = CGColorGetComponents(color);
  733. switch (model) {
  734. case kCGColorSpaceModelMonochrome: {
  735. rgba[0] = components[0];
  736. rgba[1] = components[0];
  737. rgba[2] = components[0];
  738. rgba[3] = components[1];
  739. break;
  740. }
  741. case kCGColorSpaceModelRGB: {
  742. rgba[0] = components[0];
  743. rgba[1] = components[1];
  744. rgba[2] = components[2];
  745. rgba[3] = components[3];
  746. break;
  747. }
  748. case kCGColorSpaceModelCMYK:
  749. case kCGColorSpaceModelDeviceN:
  750. case kCGColorSpaceModelIndexed:
  751. case kCGColorSpaceModelLab:
  752. case kCGColorSpaceModelPattern:
  753. case kCGColorSpaceModelUnknown:
  754. // TODO: kCGColorSpaceModelXYZ should be added sometime after Xcode 10 release.
  755. default: {
  756. #if RCT_DEBUG
  757. // unsupported format
  758. RCTLogError(@"Unsupported color model: %i", model);
  759. #endif
  760. rgba[0] = 0.0;
  761. rgba[1] = 0.0;
  762. rgba[2] = 0.0;
  763. rgba[3] = 1.0;
  764. break;
  765. }
  766. }
  767. }
  768. NSString *RCTColorToHexString(CGColorRef color)
  769. {
  770. CGFloat rgba[4];
  771. RCTGetRGBAColorComponents(color, rgba);
  772. uint8_t r = rgba[0] * 255;
  773. uint8_t g = rgba[1] * 255;
  774. uint8_t b = rgba[2] * 255;
  775. uint8_t a = rgba[3] * 255;
  776. if (a < 255) {
  777. return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, a];
  778. } else {
  779. return [NSString stringWithFormat:@"#%02x%02x%02x", r, g, b];
  780. }
  781. }
  782. // (https://github.com/0xced/XCDFormInputAccessoryView/blob/master/XCDFormInputAccessoryView/XCDFormInputAccessoryView.m#L10-L14)
  783. NSString *RCTUIKitLocalizedString(NSString *string)
  784. {
  785. NSBundle *UIKitBundle = [NSBundle bundleForClass:[UIApplication class]];
  786. return UIKitBundle ? [UIKitBundle localizedStringForKey:string value:string table:nil] : string;
  787. }
  788. NSString *RCTHumanReadableType(NSObject *obj)
  789. {
  790. if ([obj isKindOfClass:[NSString class]]) {
  791. return @"string";
  792. } else if ([obj isKindOfClass:[NSNumber class]]) {
  793. int intVal = [(NSNumber *)obj intValue];
  794. if (intVal == 0 || intVal == 1) {
  795. return @"boolean or number";
  796. }
  797. return @"number";
  798. } else {
  799. return NSStringFromClass([obj class]);
  800. }
  801. }
  802. NSString *__nullable RCTGetURLQueryParam(NSURL *__nullable URL, NSString *param)
  803. {
  804. RCTAssertParam(param);
  805. if (!URL) {
  806. return nil;
  807. }
  808. NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:YES];
  809. for (NSURLQueryItem *queryItem in [components.queryItems reverseObjectEnumerator]) {
  810. if ([queryItem.name isEqualToString:param]) {
  811. return queryItem.value;
  812. }
  813. }
  814. return nil;
  815. }
  816. NSURL *__nullable RCTURLByReplacingQueryParam(NSURL *__nullable URL, NSString *param, NSString *__nullable value)
  817. {
  818. RCTAssertParam(param);
  819. if (!URL) {
  820. return nil;
  821. }
  822. NSURLComponents *components = [NSURLComponents componentsWithURL:URL resolvingAgainstBaseURL:YES];
  823. __block NSInteger paramIndex = NSNotFound;
  824. NSMutableArray<NSURLQueryItem *> *queryItems = [components.queryItems mutableCopy];
  825. [queryItems enumerateObjectsWithOptions:NSEnumerationReverse
  826. usingBlock:^(NSURLQueryItem *item, NSUInteger i, BOOL *stop) {
  827. if ([item.name isEqualToString:param]) {
  828. paramIndex = i;
  829. *stop = YES;
  830. }
  831. }];
  832. if (!value) {
  833. if (paramIndex != NSNotFound) {
  834. [queryItems removeObjectAtIndex:paramIndex];
  835. }
  836. } else {
  837. NSURLQueryItem *newItem = [NSURLQueryItem queryItemWithName:param value:value];
  838. if (paramIndex == NSNotFound) {
  839. [queryItems addObject:newItem];
  840. } else {
  841. [queryItems replaceObjectAtIndex:paramIndex withObject:newItem];
  842. }
  843. }
  844. components.queryItems = queryItems;
  845. return components.URL;
  846. }
  847. RCT_EXTERN NSString *RCTDropReactPrefixes(NSString *s)
  848. {
  849. if ([s hasPrefix:@"RK"]) {
  850. return [s substringFromIndex:2];
  851. } else if ([s hasPrefix:@"RCT"]) {
  852. return [s substringFromIndex:3];
  853. }
  854. return s;
  855. }
  856. RCT_EXTERN BOOL RCTUIManagerTypeForTagIsFabric(NSNumber *reactTag)
  857. {
  858. // See https://github.com/facebook/react/pull/12587
  859. return [reactTag integerValue] % 2 == 0;
  860. }
  861. RCT_EXTERN BOOL RCTValidateTypeOfViewCommandArgument(
  862. NSObject *obj,
  863. id expectedClass,
  864. NSString const *expectedType,
  865. NSString const *componentName,
  866. NSString const *commandName,
  867. NSString const *argPos)
  868. {
  869. if (![obj isKindOfClass:expectedClass]) {
  870. NSString *kindOfClass = RCTHumanReadableType(obj);
  871. RCTLogError(
  872. @"%@ command %@ received %@ argument of type %@, expected %@.",
  873. componentName,
  874. commandName,
  875. argPos,
  876. kindOfClass,
  877. expectedType);
  878. return false;
  879. }
  880. return true;
  881. }